Blog-Archiv

Montag, 23. März 2020

Java Mockito Null Parameter Gotcha

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:

  1. The testNullParameter() test fails because a call with a null parameter does not match the behavior learnt in when(). And what behavior means is determined by the library, not by you. The correct name for anyXXX() would be anyXXXExceptNull() now.

  2. 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.



Keine Kommentare: