If you have used JMock a lot, you've probably had the experience of writing dozens of stubs wiring together dummy- and mock-objects.
For example:
final String DUMMY_USER_ID = "userID-1"; Mock mockUser = mock(User.class); Mock mockUnit = mock(FulfillmentUnit.class); ... Mock mockLineItem = mock(LineItem.class); Mock mockRequisition = mock(Requisition.class); Mock mockSite = mock(Site.class); Site dummySite = (Site) mockSite.proxy(); mockUnit.stubs().method("getLineItem").will(returnValue(mockLineItem.proxy())); mockLineItem.stubs().method("getRequisition").will(returnValue(mockRequisition.proxy())); mockRequisition.stubs().method("getSite").will(returnValue(dummySite)); mockUser.stubs().method("getSite").will(returnValue(dummySite)); mockUser.stubs().method("getUserID").will(returnValue(DUMMY_USER_ID)); ... mockRemoteServer.expects(once()).method("createSession").with(eq(DUMMY_USR_ID));
Using the dummy-data generating tools described here, the above code reduces to:
User user = (User) mimicWithDummyValues(User.class); FulfillmentUnit unit = (FulfillmentUnit) mimicWithDummyValues(FulfillmentUnit.class); ... precondition(unit.getLineItem().getRequisition()) .method("getSite").will(returnValue(user.getSite())); ... assertBehavior(remoteServer).expects(once()).method("createSession(with(eq(user.getUserID))));
The 'dummy' objects in the second version are just the proxies of org.jmock.Mocks. However, they have a special default stub: a random value generating stub.
The random value generating stub is similar to the DefaultResultStub: it only comes into play when a method is invoked for which no explicit stub has been provided. However, instead of user.getUserID() returning null as with the DefaultResultStub, the dummy value generating stub will return "userID-1": a much more useful value! If you create a second user2 = mimicWithDummyValues(User.class), however, user2.getUserID() will return "userID-2".
This means you can eliminate all code that is just creating or 'wiring up' dummy values.
In most situations, the following three methods are all that are needed:
proxy = mimic(type) | creates a mock and returns its proxy |
proxy = mimicWithDummyValues(type) | creates a Mock with a dummy-value-generating default stub and returns its proxy |
assertBehavior(proxy) | returns the Mock for the proxy. It is a synonym for mockForProxy(_proxy_) |
precondition(proxy) | short-hand for mockForProxy(proxy).stubs() |
The object returned by mimicWithDummyValues() is the proxy of of a standard jMock Mock. However, that mock has a default stub that is a dummy-value-generating stub. (A Mock's default stub handles all invocations that don't match any of the explicitly set stubs and expectations.)
The dummy-value-generating stub generate a unique (or random) result for all method invocations. However, the value is cached so that subsequent identical invocations will yield the same value.
For string valued methods, it generates a unique string built from the mock's name, the method name and a sequence number. For object-valued methods it will create a new dummy object.
All the stubs delegate value creation to a shared DummyValueFactory. (Currently called DummyValueGeneratingStubFactory.)
Test cases break down into 5 sections:
Write the production method invocation (4) first. To do this, you will need to create some dummies (1) to pass as parameters.
If you are writing your test before the production code, you will have to go write some of the latter at this point.
Next run the test. Start adding pre-conditions (2) to the dummies so that the code being tested follows the correct path.
Once your invocation gets all the way through and returns, you are ready to start adding expectations (3) and post-conditions (5).
Avoid explicitly creating new dummies. Usually you will only need to explicitly create dummies that are passed as input to the method-under-test (or needed to create the object-under-test).
Implicitly create the rest when specifying preconditions (or asserting behavior) by invoking the same method that the code being tested uses.
Let the dummies do the work of creating test data! For example:
assertEqual(order.getLineItem(i).getQuantity(), newRequisition.getLineItem(i).getQuantity());
If a method returns an array value, a random array of size > 0 and no null values will be returned.
Methods returning a Collection, however, will fail if invoked. In this case, you usually have to create an explicit stub.
Classes with private constructors, such as enumeration constants, or no default constructor will also fail. As always, you can write an explict stub for such methods. For collections, there are helper functions:
// Stub that returns a random collection precondition(order).method("getLineItems").will(returnValue( fillWithDummies(new ArrayList(), 5) );
Or you can write a Generator for the class as explained below.
Before DummyValueFactory creates a dummy for a given Class, it first checks to see if it has a Generator for the Class. It has built-in generators for all most common datatypes (int, BigDecimal, String). You can also implement your own generators for any class by with DummyValueFactory#addGenerator(Class, Generator).
// Example generator for an Enumeration type final ReasonCode[] reasonCodes = {ReasonCode.FRAUD, ReasonCode.DAMAGED,}; dummyValueFactory.addGenerator(ReasonCode.class, new DummyValueFactory.Generator() { Object generateValue(String label) { int i = dummyValueFactory.getRandom().nextInt(reasonCodes.size()); return reasonCodes[i]; } });