The Mockito library
is the most popular Java unit test helper in these days.
But even with popular frameworks you sometimes encounter surprising effects.
This Blog is about a debugging session that ended in a big Hello when reading the
JavaDoc
of Mockito.anyString()
, which serves for simulating parameters in mock method calls.
Anyone that doubts the usefulness of JavaDoc should read along,
together with the Mockito addicts.
For more Mockito gotchas you may also want to read
this article
about @InjectMocks
.
We are walking on thin ice.
Example Test
I want to test a name-converter.
My NameConverter
class provides a convertName(String name)
method which
I want to call to assert that it works without exception.
Aim is to show the effect of
Mockito.anyString()
,
and all other available anyXXX()
methods,
when passing either a non-null or a null value to an actual mock-instance.
Reason is that their behaviour changed since version 2.1.0,
which may break also your tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import static org.junit.Assert.*; import static org.mockito.Mockito.*; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class MockitoNullParameterValueGotcha { public static class NameConverter { public String convertName(String name) { return name; } } @Mock private NameConverter nameConverter; private final String EXPECTED_NAME = "Sloppy"; @Before public void initMocks() { MockitoAnnotations.initMocks(this); when(nameConverter.convertName(anyString())).thenReturn(EXPECTED_NAME); } @Test public void testNonNullParameter() { assertEquals(EXPECTED_NAME, nameConverter.convertName("Sloop John B.")); // success! } @Test public void testNullParameter() { assertEquals(EXPECTED_NAME, nameConverter.convertName(null)); // fails with Mockito since 2.1.0! /* * JavaDoc of Mockito.anyString(): * * Since Mockito 2.1.0, only allow non-null String. * As this is a nullable reference, the suggested API * to match null wrapper would be isNull(). * We felt this change would make tests much safer * than it was with Mockito 1.x. */ } } |
I statically import all static methods from Assert
and Mockito
on line 1 and 2, so that I can write when(....)
instead of Mockito.when(....)
.
On line 8 the unit test class starts.
On line 10 there is the tested class NameConverter
, implemented as inner class.
I allocate an instance through the @Mock
annotation upon field
nameConverter
on line 18,
and the @Before
method initMocks()
on line 23,
where the NameConverter
mock instance gets created by Mockito
through initMocks(this)
on line 24.
On line 25 I teach the mock how to behave,
I tell it that it always should return "Sloppy"
when convertName()
gets called.
For this 'stubbing' (=teaching) I use anyString()
to express that the actual parameter value should not not determine the return in any way.
Mind that the method call convertName(anyString())
goes to a mock object,
and thus is not a call to a real NameConverter
object, it just should look like.
They call this "intuitive reading".
So far the test class initialization.
The actual tests are the methods below, annotated by @Test
.
And here the gotcha starts, because the return is not always "Sloppy"
when I call the convertName
method!
The testNonNullParameter()
is successful as expected.
But testNullParameter()
is not.
It fails when you use a Mockito above 2.1.0.
Message is
expected:<Sloppy> but was <null>
Null
is what Mockito returns from a call that learnt no behaviour.
Read the comment on line 42 (extracted from JavaDoc) to understand
the new behaviour of anyString()
.
Conclusion
We need to understand two things:-
The
testNullParameter()
test fails because a call with a null parameter does not match the behavior learnt inwhen()
. And what behavior means is determined by the library, not by you. The correct name foranyXXX()
would beanyXXXExceptNull()
now. - Source code of such tests is not so easy to read and understand as the authors assume, and by that mismatch we may have efforts maintaining just our tests(!), not our application.