It’s only been a few days since the Mockito 1.8.3 release and I have to say that for a minor release there’s a lot I like about it!
What I like about the new annotations is the ability to have test cases that are completely free of @Before. Observe this example of a class that takes a text input string, translates it to an AuctionEvent, and fires it off to an AuctionEventListener:
@RunWith(MockitoJUnitRunner.class)
public class AuctionMessageTranslatorTest {
@Mock AuctionEventListener listener;
@InjectMocks AuctionMessageTranslator translator = new AuctionMessageTranslator();
@Test
public void shouldSendAnEventToTheListener(){
translator.sendMessage("SOL Version: 1.1; Event: PRICE;");
verify(listener).handleEvent(any(AuctionEvent.class));
}
}
This is a first step, to just verify an event is passed to the listener (we don’t care about it’s contents yet). @Mock creates a mock on each test method run, and @InjectMocks will pass mocks to any matching setters or constructors.
Now I’ll implement a little code to make the example pass.
public class AuctionMessageTranslator {
private AuctionEventListener listener;
public void setListener(AuctionEventListener listener) {
this.listener = listener;
}
public void sendMessage(String message) {
listener.handleEvent(new AuctionEvent());
}
}
Doesn’t do much… so let’s add a new example that verifies the contents of the message sent to the listener. Since this object is created by the translator (translating a string to an object) we’ll use an argument captor to capture and verify it’s value.
@RunWith(MockitoJUnitRunner.class)
public class AuctionMessageTranslatorTest {
@Mock AuctionEventListener listener;
@Captor ArgumentCaptor<AuctionEvent> arg;
@InjectMocks AuctionMessageTranslator translator = new AuctionMessageTranslator();
@Test
public void shouldSendAnEventToTheListener(){
translator.sendMessage("SOL Version: 1.1; Event: PRICE;");
verify(listener).handleEvent(any(AuctionEvent.class));
}
@Test
public void shouldSendAnEventWithNamePrice(){
translator.sendMessage("SOL Version: 1.1; Event: PRICE;");
verify(listener).handleEvent(arg.capture());
assertThat(arg.getValue().getName(), equalTo("PRICE"));
}
}
It fails, so we implement the code to make it pass:
public void sendMessage(String message) {
listener.handleEvent(parseEvent(message));
}
private AuctionEvent parseEvent(String message) {
AuctionEvent auctionEvent = new AuctionEvent();
auctionEvent.setName(message.split(";")[1].split(":")[1].trim());
return auctionEvent;
}
This passes as the argument passed to the listener does indeed contain the event name. This is a little ugly, so let’s refactor it a little bit with our test providing a nice safety net:
public void sendMessage(String message) {
listener.handleEvent(parseEvent(message));
}
private AuctionEvent parseEvent(String message) {
AuctionEvent auctionEvent = new AuctionEvent();
auctionEvent.setName(unpackMessage(message).get("Event"));
return auctionEvent;
}
private Map<String, String> unpackMessage(String message) {
Map<String, String> pairs = new HashMap<String, String>();
for(String pairString : message.split(";")){
String[] pair = pairString.split(":");
pairs.put(pair[0].trim(), pair[1].trim());
}
return pairs;
}
Looks good, and the test case for it is pretty clean although it has a lot of annotations. We could change the injection strategy to use constructor injection since we don’t really want the object to even exist without a listener:
@RunWith(MockitoJUnitRunner.class)
public class AuctionMessageTranslatorTest {
@Mock AuctionEventListener listener;
@Captor ArgumentCaptor<AuctionEvent> arg;
@InjectMocks AuctionMessageTranslator translator = new AuctionMessageTranslator(listener);
...
}
As long as the @InjectMocks annotation is present, the MockitoJunitRunner will initialize the mocks do they’re available for injection. Drop the @InjectMocks annotation off, and it fails with a null pointer exception.
One interesting thing of note when using @InjectMocks with setter injection is if you do something silly like the following:
@RunWith(MockitoJUnitRunner.class)
public class AuctionMessageTranslatorTest {
@Mock AuctionEventListener listener;
@Mock AuctionEventListener listener2;
@Captor ArgumentCaptor>AuctionEvent> arg;
@InjectMocks AuctionMessageTranslator translator = new AuctionMessageTranslator();
...
}
It will inject the first @Mock, not the second. Verifications against listener will work, while verifications against listener2 will fail as it was never injected.
Tomorrow I’ll include some examples of using the @Spy annotation as well as the different answer types you can configure @Mock annotated mocks with as of 1.8.3. ![]()