When starting to write an unit test, it could be easy to use a framework which will easily mock dependencies for you, so that you are creating isolated tests for each class.

A well-known framework for Java that can handle this, is the Mockito framework.
In combination with a Spring Boot application this framework can inject mocks & spies easily to the class you are testing on.

So what is the difference between mocks & spies in the context of the Mockito framework? See also https://martinfowler.com/bliki/TestDouble.html for an abstract description of all related test terms that are used.

  • A mock is a fully stubbed service, which will do nothing if you are not telling it to do so. You may change the behaviour of the responses & also verify if a specific method is called.
  • A spy can verify if a specific method is called, but the behaviour is not changed (if you are not mocking the method, but that makes it even more complicated).
Best practice is to use mocks & only use spies if really needed; for example in legacy code (and probably in combination with complicated integrationTests).

Step-by-step guide

If we have a simple Spring Boot service like this:

@Component
public class ExampleService{
	private final DependentService dependentService;
	@Autowired
	public ExampleService(DependentService dependentService){
		this.dependentService = dependentService;
	}
	
	public String helloWorld(){
		String hello = "Hello";
		World world = dependentService.getWorld();
		return hello + " " + world.getName();
	}
}


//(Separate class file)
public class World{
	private String name;

	public World(String name){
		this.name = name;
	}
	//Other setters & information
	public String getName(){
		return name;
	}
}

We have a dependent service DependentService, which we can mock, so that any functionality of that service is separately tested in its own unit test:

@RunWith(MockitoJUnitRunner.class)
public class ExampleServiceTest{
	@InjectMocks
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	
	@Test
	public void shouldCreateHelloWorldString(){
		//given
		World world = new World("world");
		when(dependentServiceMock.getWorld()).thenReturn(world);
		//alternative notation is given(dependentService.getWorld()).willReturn(world)
		//personally preferring this last (although less used) notation, because of the BDD (behaviour driven design) pattern...
		
		//when
		String example = exampleSUT.helloWorld(); //actual call to the service method

		//then
		assertEquals("Hello world", example);
		verify(dependentServiceMock, times(1)).getWorld();
	}
}

First, you can see that we run the test with the MockitoJUnitRunner class. This runner ensures that the service under test (SUT) will get the mocks injected.
The SUT will therefore have the @InjectMocks annotation, so that Mockito knows which service should have the injections.
The services that should be mocked or spied, should be annotated with @Mock or @Spy respectively.

Be aware, that any dependent service not inserted as a @Mock field in the test class, will be injected as null. This can give NullPointerExceptions when called in the service under test method.

In the test method itself, we are going to mock the method call we use of the dependentService. In order to do so, we use the when().thenReturn(); or given().willReturn(); static method of Mockito. In the when you put the service & method call and in the thenReturn you put the argument that will be given back.
Please note that void methods are not accepted in this context. In order to do logic with these methods (for example, throw an exception or change parameters), you can use methods like doAnswer(); or doThrowException();. For further information about these features, please see the docs.

After you have given each expected return value of the mocked calls, you can do the real test call.

Then you do your assertions. Just regular ones are possible. Other assertions you may make, are verifications of calls that are made to the mock service(s). You can use verify() to do so. Be aware, that – in contrast with the when/given – the method call itself is next to the verify method. So not verify(dependentServiceMock.getWorld()), but verify(dependentServiceMock).getWorld(). You may also give a parameter with times(x), where x is the total times that method is called. If you expect a method to not be called (in conditional cases), you can also use never(). You have also other conditions such as atLeastOnce(). See docs for more info on them.

So now you have a test that:

  • Runs with the MockitoJUnitRunner, in order to:
  • Has an ExampleService Under Test instantiated by Mockito by @InjectMocks
  • Has a DependentService mock annotated by @Mock, which will automatically will be injected in the SUT
  • Will mock the getWorld method of the DependentService mock
  • Calls the SUT as expected by a test
  • Does regular assertions undependent of the Mockito framework
  • Does verifications of service mock calls that they are actually called

Manual instantiation of the SUT

Another option to instantiate a service under test is the following:

...	
public class ExampleServiceTest{
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	
	@Before
	public void before(){
		exampleService = new ExampleService(dependentServiceMock);
	}
...

Thus not using Mockito’s InjectMocks to instantiate the ExampleService. The usage of this can be, because we have for example primitive fields, which cannot be injected by a mock.

Because injection of primitive fields is not automatically possible, it is preferred to write Spring Boot services with the @Autowired annotation on the constructors instead of on all the fields (which is possible), so that you can always instantiate a SUT with your custom services & properties. (Else you are prone to using reflections to do so…)

Mocking other objects (than services)

In the example we instantiate the World object, to return it in the mock method. But we can also mock the World object. The advantage of this is, that we only need to define the methods which are used by the SUT for this object. Furthermore, there are two options to create the mock for this object. We can create a field for the mock just like for the dependentService. But we could also use the static method mock(Object.class); to generate a mock for the object. It depends on the usage of the object which you use. Most of the times, the objects are re-used in other tests (best practice is to keep tests small, so re-usage will be high). In that case, using as field with @Mock will be preferred. This will make the following for our example:

@RunWith(MockitoJUnitRunner.class)
public class ExampleServiceTest{
	@InjectMocks
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	@Mock
	private World worldMock;
	
	@Test
	public void shouldCreateHelloWorldString(){
		//given
		when(worldMock.getName()).thenReturn("world");
		when(dependentServiceMock.getWorld()).thenReturn(worldMock);
		
		//when
		String example = exampleSUT.helloWorld();

		//then
		assertEquals("Hello world", example);
		verify(dependentServiceMock, times(1)).getWorld();
		verify(worldMock, times(1)).getName();
	}
}

See how we also verify the world mock object on it’s call? That is the advantage of using the mock instead of instantiating the object.

Passing & matching arguments

Mocking methods without parameters is easy, but for mocking with there are some extra things to consider. When we have a method like:

public String dependentService.getCountryCode(String countryName);

And we need to mock this method, we have several options:

when(dependentServiceMock.getCountryCode("Netherlands")).thenReturn("NL"); //Will return only NL when Netherlands is passed
when(dependentServiceMock.getCountryCode(eq("Netherlands")).thenReturn("NL"); //Will return only NL when Netherlands is passed
when(dependentServiceMock.getCountryCode(anyString()).thenReturn("NL"); //Will return NL when any string is passed
when(dependentServiceMock.getCountryCode(any(String.class)).thenReturn("NL"); //Will return NL when any string is passed

As you can see, the first two methods will return on a specific argument. The latter two will not. Be aware that in most cases you would like to test specific values and thus use line 1 or 2. The times I use the other two is, in a @Before to create a default return value (if you are never expecting to get a null that is).

When you use eq(), it is important that with more than one parameter in the method, you should use these Matchers as well for the other arguments. There are other Matchers, for example same(), any(), startsWith()… For a full list you can always look at the Mockito Matchers class.

In verifications you can do the same, so you can decide how specific you are going to assert the arguments. Best practice is always to be as specific as possible.

Is an object passed to some method where you get it also back and want to keep the object (instead of returning null)? This is also possible:

public String helloWorld(){
	World world = new World("world");
	world = dependentService.saveOrUpdate(world);
	return "Hello" + " " + world;  
}
@RunWith(MockitoJUnitRunner.class)
public class ExampleServiceTest{
	@InjectMocks
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	
	@Test
	public void shouldCreateHelloWorldString(){
		//given
		when(dependentServiceMock.saveOrUpdate(any(World.class)).thenAnswer(invocation -> invocation.getArgumentAt(0, World.class));
		
		//when
		...
	}
}

ArgumentCaptors

To verify an object passed by a mocked service, we can use argumentcaptors. If we have the following example service:

@Component
public class ExampleService{
	private final DependentService dependentService;
	@Autowired
	public ExampleService(DependentService dependentService){
		this.dependentService = dependentService;
	}
	
	public String helloWorld(){
		World world = new World("world", false);
		//... do other logic stuf ...
		if(!world.isWorld()){ //Of course, isWorld will always be false in this case...
			world.setName("not world");
		}
		dependentService.saveOrUpdate(world);
		return "Hello" + " " + world.getName();
	}
}


//(Separate class file)
public class World{
	private String name;
	private boolean isWorld;

	public World(String name, boolean isWorld){
		this.name = name;
		this.isWorld = isWorld;
	}

	...//getters & setters
}

We cannot mock the world object, because it is instantiated in the method itself. If we want to check it’s value when saved, we need to “capture” the argument from the dependentService call. In order to so, we can create an argumentCaptor:

@RunWith(MockitoJUnitRunner.class)
public class ExampleServiceTest{
	@InjectMocks
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	@Captor
	private ArgumentCaptor<World> argumentCaptorWorld;

	@Test
	public void shouldCreateHelloWorldString(){
		//given nothing in this case
		
		//when
		String example = exampleSUT.helloWorld();

		//then
		assertEquals("Hello not world", example);
		verify(dependentServiceMock, times(1)).saveOrUpdate(argumentCaptorWorld.capture());
		World capturedWorld = argumentCaptorWorld.getValue();
		assertEquals("not world", capturedWorld.getName());
		assertTrue(capturedWorld.isWorld());
	}
}

When the dependentServiceMock.saveOrUpdate would be called more than once (for example when going through a list of World’s), you can also use the ArgumentCaptor’s getAllValues(); you will get a list of all captured objects and can do validation on them.

Throw exceptions

Sometimes you want to test the throwing of exceptions:

@RunWith(MockitoJUnitRunner.class)
public class ExampleServiceTest{
	@InjectMocks
	private ExampleService exampleSUT;
	@Mock
	private DependentService dependentServiceMock;
	
	@Test(expected = RuntimeException.class)
	public void shouldThrowException(){
		//given
		World world = new World("world");
		when(dependentServiceMock.getWorld()).thenThrow(new RuntimeException("Something bad happened"));

		//when
		exampleSUT.helloWorld(); //actual call to the service method

		//then nothing to assert anymore
	}
}

Usage of spies

In the case you have to use spies, be aware that you cannot mock methods, only verify them:

...
	@Spy
	private DependentService dependentServiceMock;
	
	@Test(expected = RuntimeException.class)
	public void shouldThrowException(){
		//given
		when(dependentServiceMock.getWorld()).thenReturn(new World("world")); //Not allowed!

		//when
		exampleSUT.helloWorld();

		//then
		verify(dependentServiceMock).getWorld(); //Possible to do
	}
...

Conclusion

Now you know the basics for mocking services in order to create isolated unit tests. Please look at the docs (or just search via Google and find StackOverflow answers) for further information & questions.

References

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *