Testing A Simple Publisher/Subscriber System With Mockito
One of the challenges I find when teaching Java testing with Mockito is that the docs, while complete, don’t motivate why you want to use mocks in the first place. This post includes a simple example to show why mocking frameworks are important and where they are useful.
As a quick summary, Mockito is a Java-based framework for creating mocks, stubs, and spies. As described in the famous blog post by Martin Fowler, Mocks Aren’t Stubs, the basic ideas for stubs and mocks are:
- A stub is a class that stands in for another and returns required outputs given specific inputs.
- A mock is used to verify the interaction between the class you are testing and a stub. It tests the protocol, meaning you check that the methods on the stub were invoked the right number of times in the right order.
The example is based on one used by the Spock testing framework, shown in the Spock Example project on GitHub. I’ve adapted it for Mockito as an illustration and slightly changed the method names. It does a great job motivating why you need mocks in the first place.
Consider a simple Publisher
, which maintains a list of Subscriber
instances and sends each them a String
message.
public class Publisher { private List<Subscriber> subscribers = new ArrayList<>(); public void subscribe(Subscriber sub) { if (!subscribers.contains(sub)) { subscribers.add(sub); } } public void send(String message) { for (Subscriber sub : subscribers) { try { sub.onNext(message); } catch (Exception ignored) { // evil, but what can you do? } } } }
The Subscriber
interface in this simplified model contains only the onNext
method:
public interface Subscriber { void onNext(String message); }
(The Publisher
class and Subscriber
interface are simplified forms of the corresponding types defined by the Reactive Streams specification and included in Java SE 9 and above. The implementations here contain only enough details to highlight why we need mocks.)
Here’s the problem: How do you test the send
method in Publisher
? It returns void
, which is always hard to test. The class has no attributes other than the list of subscribers, so you can’t look for a change of state. Subscriber
s only have an onNext
method which also returns void
, so there’s nothing coming back. Worst of all, if the Subscriber
throws an exception, the Publisher
catches it and ignores it.
The only way to check that the send
method is working properly is to verify that the onNext
method in each subscriber has been invoked exactly once with the proper argument. That’s what mocks do.
(Note that the following tests use JUnit 5.)
The PublisherTest
starts by initializing a Publisher
and adding two mock Subscriber
instances:
public class PublisherTest { private final Publisher pub = new Publisher(); private final Subscriber sub1 = mock(Subscriber.class); private final Subscriber sub2 = mock(Subscriber.class); @BeforeEach public void setUp() { pub.subscribe(sub1); pub.subscribe(sub2); } // ... tests to come ... }
In this particular case, I don’t need to set any expectations on the stubs, because the onNext
method doesn’t return anything. I just need to make sure the method is called exactly once on each subscriber with the proper argument. That’s done with a simple verify:
@Test public void testSend() { pub.send("Hello"); verify(sub1).onNext("Hello"); verify(sub2).onNext("Hello"); }
The verify
method checks that onNext
was invoked on each subscriber with the String
argument Hello
.
If we assume that one of the subscribers fails and throws an exception every time, the awful “catch and ignore” behavior of the send
method can be verified as well:
@Test public void testSendWithBadSubscriber() { // sub1 throws an exception every time doThrow(RuntimeException.class).when(sub1).onNext(anyString()); pub.send("message 1"); pub.send("message 2"); // sub2 still receives the messages verify(sub2, times(2)) .onNext(argThat(s -> s.matches("message \\d"))); }
In this case, the doThrow
method is used to make the first subscriber throw a runtime exception whenever its onNext
method is invoked with any string, using the built-in argument matcher anyString
.
The verify
method this time checks that the onNext
method was invoked twice on the second subscriber. The argument employs a custom matcher. Mockito uses the argThat
method to let you provide a custom argument matcher, which must implement the ArgumentMatcher
interface. That interface has only a single abstract method:
public interface ArgumentMatcher<T> { boolean matches(T argument); }
The ArgumentMatcher
is thus a functional interface, so its implementation can be provided with a lambda expression. In the test above, the custom matcher checks that the argument consists of the word "message"
followed by a space and a digit.
The testSendWithBadSubscriber
method only checks that the second subscriber received the proper message. In principle you could add assertions to verify that the first subscriber threw the exception. This is particularly easy to do with JUnit 5, which includes an assertThrows
method (and an assertAll
method to ensure both onNext
invocations are checked even if one fails):
@Test public void testSendWithBadSubscriber() { // ... after setting expectation and invoking pub.send(...) ... assertAll( () -> assertThrows(RuntimeException.class, () -> sub1.onNext("message 1")), () -> assertThrows(RuntimeException.class, () -> sub1.onNext("message 2"))); // ... verify from before ... }
It’s not actually necessary, however, to check that the stubs do what you tell them to do when their expectations were set earlier in the same method. Still, it doesn’t hurt anything either.
The tests check that the publisher’s send
method is behaving as designed, through the behavior of the two subscribers. Honestly, it’s hard to think of any other way to do that. Hopefully this gives you a sense of when mocks can be useful.
All the code, along with many more examples I use in my training course on Mockito and the Hamcrest Matchers, is contained this GitHub repository.
Leave a Reply