We've seen how to
* setup expectations for calls on methods/properties.
* specify the number of times to be called.
* specify values to be returned
Now I move onto more involved scenarios.
#6:Throw an exception in response to an expected call on mockCollaborator.Method()
- Rhino Mocks
[ExpectedException(ExceptionType=typeof(UnableToServeException),
MatchType=MessageMatch.Contains,
ExpectedMessage="Sorry for the inconvenience.")]
[Test]
public void Test_ExpectExceptionToBeThrown()
{
_mockChef.Expect(chef => chef.Bake(CakeFlavors.Pineapple, false)).Throw(new Exception());
_bakery.PlaceOrder(OrderForOnePineappleCakeNoIcing);
}
The Throw method tagged onto the end of the Expect does the trick of raising any derivation of Exception (here I've raised a generic Exception)
- Moq
[ExpectedException(ExceptionType=typeof(UnableToServeException),
MatchType=MessageMatch.Contains,
ExpectedMessage="Sorry for the inconvenience.")]
[Test]
public void Test_ExpectExceptionToBeThrown()
{
_mockChef.Setup( chef => chef.Bake(CakeFlavors.Pineapple, false)).Throws<Exception>();
_bakery.PlaceOrder( OrderForOnePineappleCakeNoIcing );
}
Moq uses a generic Throws
- jMock
@Test
public void test_expectAndThrowException() throws OutOfIngredientsException
{
final Sequence aSequence = context.sequence("retryBake");
context.checking( new Expectations() {{
oneOf(_mockChef).bakeRealWorld( CakeFlavors.Pineapple, false );
will(throwException(new OutOfIngredientsException() ));
inSequence(aSequence);
oneOf(_mockInventory).replenishStocks();
inSequence(aSequence);
oneOf(_mockChef).bakeRealWorld( CakeFlavors.Pineapple, false );
inSequence(aSequence);
}});
_bakery.placeOrderRealWorld( OrderForOnePineappleCakeNoIcing() );
}
The will(throwException(exceptionInstance) after an expectation does the trick. Ignore the lines in Sequence (I'll get to them in Section#9)
Java's checked exceptions and explicit method specification (all the way up the call-chain) feels a bit too rigid to me (maybe due to my .Net exposure). So I've created an overload of Bake that throws an exception, to avoid adding a throws clause to every test method that has an expectation for Bake(). Worked for me.. although it's not the right thing to do. The joys of coding in a toy example!
void bakeRealWorld(CakeFlavors flavor, boolean withIcing) throws OutOfIngredientsException;
#7:Custom callbacks or blocks of code to execute when a mockCollaborator.Method() is called
Let me cook up a use for this. Let's say I want the first call to Chef.Bake throw an OutOfIngredientsException(), so that I can call ReplenishStocks() and retry. This time, the call to Chef.Bake should go through.
Just the kind of job for... custom Callbacks!
- Rhino Mocks
[Test]
public void Test_ExpectWithCustomCallback()
{
int callbacks = 0;
_mockChef.Expect(chef => chef.Bake(CakeFlavors.Pineapple, false))
.Repeat.Times(2)
.WhenCalled(delegate(MethodInvocation mi) {
Console.WriteLine("Callback Params: {0}, {1}", mi.Arguments[0], mi.Arguments[1]);
callbacks += 1;
if (callbacks == 1)
throw new OutOfIngredientsException();
return;
});
_mockInventory.Expect(inv => inv.ReplenishStocks()).Return(true);
_bakery.PlaceOrder(OrderForOnePineappleCakeNoIcing);
_mockChef.VerifyAllExpectations();
_mockInventory.VerifyAllExpectations();
}
Tag on WhenCalled(delegateWithCustomCode) to the expectation. Also note that you have access to the method arguments as shown in the Console trace above.
- Moq
[Test]
public void Test_ExpectWithCustomCallback()
{
var callbacks = 0;
_mockChef.Setup(chef => chef.Bake(CakeFlavors.Pineapple, false))
.Callback( delegate(CakeFlavors flavor, bool withIcing)
{ // any custom code
Console.WriteLine("Callback Params: {0}, {1}", flavor, withIcing);
callbacks += 1;
if (callbacks == 1)
throw new OutOfIngredientsException();
return;
}
);
_mockInventory.Setup(inv => inv.ReplenishStocks()).Returns(true);
_bakery.PlaceOrder( OrderForOnePineappleCakeNoIcing );
_mockChef.VerifyAll();
_mockInventory.VerifyAll();
}
Tag on an overload of Callback( customDelegate ) at the end of the Setup to specify custom code to be executed.
Moq gives you strongly typed callbacks with upto 4 parameters (Action
- jMock
// http://www.jmock.org/custom-actions.html
@Test
public void test_expectAndInvokeCustomCallback()
{
context.checking( new Expectations() { {
oneOf(_mockChef).bake(CakeFlavors.Pineapple, false );
will( MyCustomCallback.tracingCallback() );
} } );
_bakery.placeOrder( new Order(CakeFlavors.Pineapple, false, 1) );
}
class MyCustomCallback implements Action
{
public void describeTo(org.hamcrest.Description description)
{
description.appendText("a custom callback to do what I deem fit! ");
}
public Object invoke(Invocation invocation) throws Throwable
{ // any custom code
System.out.println("Callback invoked with "
+ ((CakeFlavors)invocation.getParameter(0))
+ " and "
+ (((Boolean) invocation.getParameter(1)).booleanValue() ? "" : "no")
+ " icing!");
return null;
}
public static Action tracingCallback()
{
return new MyCustomCallback();
}
}
It may be due to my rusty Java but it turned out to be a whole lot of classes to achieve this with jMock
#8:Raise an event from a mock collaborator
This one is very handy when testing GUIs using a ModelViewPresenter (or related pattern)
Our inventory raises an event when we're running low on a particular ingredient. The bakery can use this notification to tell the Chef to go easy on that ingredient, while we stock our supplies.
- Rhino Mocks
[Test]
public void Test_RaiseEventFromMock()
{
_mockChef.Expect(chef => chef.GoEasyOn("Sugar"));
_mockInventory.Raise(inv => inv.RunningLowOnIngredient += null,
this, new StockEventArgs("Sugar"));
_mockChef.VerifyAllExpectations();
}
To raise an event, invoke the Raise method on the mock with a null subscription to the event & the arguments to be passed onto the event handler method.
- Moq
[Test]
public void Test_RaiseEventFromMock()
{
_mockChef.Setup(chef => chef.GoEasyOn("Sugar"));
_mockInventory.Raise(inventory => inventory.RunningLowOnIngredient += null, new StockEventArgs("Sugar"));
_mockChef.VerifyAll();
}
Identical to Rhino except for the fact that you don't have to specify the 'sender' parameter.
- jMock
Java doesn't support events as first-class citizens. You need to manually implement the publisher-subscriber pattern.
#9:Argument Constraints
Sometimes, we do not want to / cannot specify the exact arguments with which a method on a collaborator will be called as part of the expectation. In such cases, we use constraints on the arguments, which must be met for the expectation to be met.
e.g. from the previous example, lets say we don't know the value of the Low-stock-ingredient at compile-time. We have a variable into which we can fetch the ingredient and which we can use to compare against the actual value.
In short we want to specify a Predicate (a function which returns true/false) for an argument as a constraint.
- Rhino Mocks
[Test]
public void Test_ConstrainingArguments()
{
var itemAboutToGoOutOfStock = "Eggs";
_mockChef.Expect(chef => chef.GoEasyOn(
Arg<string>.Matches (ingredient => ComplexCheck(itemAboutToGoOutOfStock, ingredient))));
_mockInventory.Raise(inventory => inventory.RunningLowOnIngredient += null,
this, new StockEventArgs(itemAboutToGoOutOfStock));
_mockChef.VerifyAllExpectations();
}
private bool ComplexCheck(string expected, string actual)
{
return expected.Equals(actual); // any complex test
}
So here we use Arg
- Moq
[Test]
public void Test_ConstrainingArguments()
{
var itemAboutToGoOutOfStock = "Eggs";
_mockChef.Setup(chef => chef.GoEasyOn(
It.Is<string>(ingredient => ComplexCheck(itemAboutToGoOutOfStock, ingredient))));
_mockInventory.Raise(inventory => inventory.RunningLowOnIngredient += null,
new StockEventArgs(itemAboutToGoOutOfStock));
_mockChef.VerifyAll();
}
private bool ComplexCheck(string expected, string actual)
{
return expected.Equals(actual); // any complex test
}
Moq has It.Is
- jMock
The example is different. Here we are constraining the arguments to the Bake call with a predicate.
//http://www.jmock.org/custom-matchers.html
@Test
public void test_expectAndCheckArgs()
{
context.checking( new Expectations() { {
oneOf(_mockInventory).isEmpty();
will(returnValue(false));
oneOf(_mockChef).bake(
with( FlavorMatcher.aPineappleFlavoredCake() ),
with( any(boolean.class)));
}} );
_bakery.pleaseDonate( OrderForOnePineappleCakeNoIcing() );
}
class FlavorMatcher extends TypeSafeMatcher<CakeFlavors>
{
public boolean matchesSafely(CakeFlavors flavor)
{ // any complex test
return flavor == CakeFlavors.Pineapple;
}
public void describeTo(org.hamcrest.Description description) {
description.appendText("a cake with pineapple flavor ");
}
@Factory
public static Matcher<CakeFlavors> aPineappleFlavoredCake()
{
return new FlavorMatcher();
}
}
once again more classes than I'd liked (Disclaimer: rusty java programmer could be the problem).
Use with(matcherInstance) in place of arguments in the expectation. Derive a new Matcher derived class with your custom check.
#9:Expect a sequence of calls in a specific order
Ordered Expectations: Looking at the test in Section#4 - the test can be satisfied by the following implementation
public void PleaseDonate(Order order)
{
_chef.Bake(order.Flavor, order.WithIcing);
var isEmpty = _inventory.IsEmpty;
}
which is not what we were getting at. Seems like the test is missing something... the order of calls! Chef.Bake should be called after Inventory.IsEmpty.
- Rhino Mocks
[Test]
public void Test_ExpectCallsInOrder()
{
var mockCreator = new MockRepository();
_mockChef = mockCreator.DynamicMock<Chef>();
_mockInventory = mockCreator.DynamicMock<Inventory>();
mockCreator.ReplayAll(); // to avoid setting up an expect for an event handler subscribe in the following ctor.
_bakery = new Bakery(_mockChef, _mockInventory);
using (mockCreator.Ordered())
{
_mockInventory.Expect(inv => inv.IsEmpty).Return(false);
_mockChef.Expect(chef => chef.Bake(CakeFlavors.Pineapple, false));
}
_bakery.PleaseDonate(OrderForOnePineappleCakeNoIcing);
_mockChef.VerifyAllExpectations();
_mockInventory.VerifyAllExpectations();
}
Woooaah! What just happened? Have a seat ; take a deep breath.
Ordered expectations have been a feature of Rhino Mocks; however the v3.5 syntax revamp hasn't made an impact here. This took some doing .. Turns out you need to go old-skool for a bit.
The key:
* Create a new MockRepository instance and invoke the DynamicMock method (instead of the static MockRepository.GenerateMock )
* Use the Ordered block to setup expectations in a specific order
- Moq
[Test]
public void Test_ExpectCallsInOrder()
{
var sequence = String.Empty;
_mockInventory.Setup(inv => inv.IsEmpty).Returns(false)
.Callback(delegate { sequence += "1"; });
_mockChef.Setup(chef => chef.Bake(CakeFlavors.Pineapple, false))
.Callback(delegate { sequence += "2"; });
_bakery.PleaseDonate(OrderForOnePineappleCakeNoIcing);
Assert.AreEqual("12", sequence, "Calls not in expected order!");
_mockChef.VerifyAll();
_mockInventory.VerifyAll();
}
Last time I checked (Jan25,2010) Moq does not support this out of the box. However you could work around this with a collecting object. Very naive.. you could come up with better solutions to fit your exact context.
- jMock
@Test
public void test_expectCallsInSpecificOrder()
{
final Sequence pleaseDonateSequence = context.sequence("pleaseDonate");
context.checking( new Expectations() {{
oneOf(_mockInventory).isEmpty();
inSequence(pleaseDonateSequence);
will(returnValue(false));
oneOf(_mockChef).bake(CakeFlavors.Pineapple, false);
inSequence(pleaseDonateSequence);
}});
_bakery.pleaseDonate( OrderForOnePineappleCakeNoIcing() );
}
jMock has the shortest syntax for ordered expectations. You create a named Sequence object and then tag the name along with each expectation (listed in the expected order).
So there you have it. If it was a showdown, jMock would be ahead of Moq and Rhino Mocks would be just a sliver behind Moq.
JMock:
+ has a nice readable concise API for number of invocations and ordered expectations
Moq:
+ has a nice mechanism for type-safe custom callbacks and argument constraints
Rhino:
+ isn't bad. Does everything. However even with the v3.5 syntax revamp, it is a little unintuitive as compared to the others. The documentation was a bit noob-unfriendly, when I needed it. However it is the only option if can't use .net 3.5 for your tests (which should be rare).
Nice comparison!!
ReplyDeleteDid you ever try typemock Isolator?
Keep on posting!
.peter.gfader.
http://peitor.blogspot.com/
http://twitter.com/peitor