Rhino Mocks: fixing AssertWasCalled for disposed objects
I have a test which looks a bit like this (I'm simplifying a bit from my real issue):
[Test]
public void Eat_calls_consumption_tracker_OnConsume()
{
var consumptionTrackerStub =
MockRepository.GenerateStub<IConsumptionTracker>();
var monkey = new Monkey(consumptionTrackerStub);
var banana = new Banana();
monkey.Eat(banana);
consumptionTrackerStub.AssertWasCalled(x => x.OnConsume(banana));
}
This would work fine, except that Monkey
disposes Banana
after eating it. Therefore, the banana object is no longer in a usable state. In particular, the Banana.Equals
implementation cannot work after Dispose
is called because it uses unmanaged resources that have been released.
Unfortunately AssertWasCalled
will cause Banana.Equals
to be c开发者_运维百科alled, which blows up, causing the test to fail. What is the best way to fix this?
I would be inclined to suggest that the class that instantiates an object should dispose of it. It's never a good idea to pass a Banana to a Monkey and then trust it to Dispose properly after consuming.
Can you not pass the data required to instantiate Banana into Monkey, rather than the Banana itself? Alternatively, can you not Dispose of it in your calling class?
What if Monkey.Eat had a parameter of IBanana (or IFood), and IBanana descended from IDisposable? Then you could mock IBanana and even verify that Dispose was called on it.
EDIT: You can do it with Moq:
[Test]
public void Eat_calls_consumption_tracker_OnConsume()
{
var consumptionTrackerStub = new Mock<IConsumptionTracker>();
var monkey = new Monkey(consumptionTrackerStub.Object);
var banana = new Banana();
monkey.Eat(banana);
consumptionTrackerStub.Verify(x => x.OnConsume(It.IsAny<Banana>()));
}
public class Banana
{
public override bool Equals(object obj)
{
throw new ObjectDisposedException("Banana");
}
}
public class Monkey
{
public Monkey(IConsumptionTracker tracker)
{
_tracker = tracker;
}
public void Eat(object obj)
{
_tracker.OnConsume(obj);
}
private readonly IConsumptionTracker _tracker;
}
public interface IConsumptionTracker
{
void OnConsume(object obj);
}
Several possible solutions:
- Maybe you could relax your assertion: do you really need to check that what's been eaten is banana?
- Use an inline callback on OnConsume and assert it's banana and not some other fruit. This callback would be executed before the banana has been disposed of.
BTW: now that I think about it, I think your code has a conceptual problem: how do you know some implementation of OnConsume will not want to keep the banana peel?
Turns out you can force Rhino Mocks to check the arguments with object.ReferenceEquals
like this:
[Test]
public void Eat_calls_consumption_tracker_OnConsume()
{
var consumptionTrackerStub =
MockRepository.GenerateStub<IConsumptionTracker>();
var monkey = new Monkey(consumptionTrackerStub);
var banana = new Banana();
monkey.Eat(banana);
consumptionTrackerStub.AssertWasCalled(
x => x.OnConsume(
Arg<Banana>.Matches(
y => object.ReferenceEquals(y, banana))));
}
object.ReferenceEquals
still works even if the banana has been disposed, thus fixing the problem.
精彩评论