"Any sufficiently advanced technology is indistinguishable from magic."
â Arthur C. Clarke
This chapter explores the advanced topics of the Mockito framework. Using Mockito's advanced features, we can stub out void methods, capture arguments passed to the stubbed methods and assert the argument values, verify the invocation order to check that the collaborators are accessed in proper order, spy a real object and set expectation on the spy object in the legacy code, and change mocking behavior.
The following topics are covered in this chapter:
Chapter 2, Socializing with Mockito, explained the external dependencies and provided examples of basic Mockito features, such as stubbing method calls, throwing exceptions, matching arguments, verifying method invocations, and answering method calls.
Mockito provides a fluent API for mocking Java objects. It offers a collection of advanced features for advanced users. This section deals with the advanced Mockito features and answers several questions, such as how to change the Mockito settings to return smart null values instead of default return types, how to reset a mock object to clear all previous information, how to determine whether an object is a spy or a mock, and how to capture arguments passed to a mock object and verify the values.
The following sections cover the advanced Mockito APIs.
Unit testing void methods is difficult. Conventional unit tests prepare data, pass values to a method, and then assert the return type to verify the behavior of the code. But when a method doesn't return a value but only changes the internal state of the object under test, it becomes difficult to decide what to assert. Conventional unit tests work with direct input and output, but void methods need to work with indirect output.
In this section, we'll examine a legacy servlet code and write unit test for the legacy code. To unit test a servlet code, you need the Servlet-apiXX.jar
, JUnit
JAR file, and the Mockito
JAR file. To download servlet-api.<version number>.jar
, you can visit the Oracle URL at http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-eeplat-419426.html, and we already have the JUnit and Mockito JAR files. On the other hand, you can download the code and associated JAR files for this chapter from the Packt website.
The following servlet code acts as a front controller. It intercepts all the web requests and delegates these requests to appropriate resources. The DemoController
servlet extends from HttpServlet
and has a dependency on a LoginController
class. The constructor creates an instance of LoginController
, as shown in the following code:
@WebServlet("/DemoController") public class DemoController extends HttpServlet { private LoginController loginController; public DemoController() { loginController = new LoginController( new LDAPManagerImpl()); } }
The doPost()
and doGet()
methods are inherited from HttpServlet
. The doPost()
method intercepts the HTTP POST requests, and delegates calls to the doGet()
method.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
The doGet()
method intercepts all the HTTP GET requests, and depending on the request context URL, it routes the requests to appropriate handlers. Initially, the login.jsp
page is opened for user login. On submission of the Login form, the /logon.do
action is taken. The loginController
class handles the /logon.do
request, and all other requests are routed to the error page. The following is the body of the doGet()
method:
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String urlContext = req.getServletPath(); if(urlContext.equals("/")) { req.getRequestDispatcher("login.jsp").forward(req, res); }else if(urlContext.equals("/logon.do")) { loginController.process(req, res); }else { req.setAttribute("error", "Invalid request path '"+urlContext+"'"); req.getRequestDispatcher("error.jsp").forward(req, res); } }
The LoginController
class has a dependency on LDAPManager
for user validation. This class handles the login request, retrieves the username and encrypted password from the HTTP request, and asks the LDAPManager
to validate whether the user exists or not. The following is the LoginController
class:
public class LoginController { private final LDAPManager ldapManager; public LoginController(LDAPManager ldapMngr) { this.ldapManager = ldapMngr; } }
The process()
method delegates user validation to LDAPManager
and if the user is valid, then it creates a new session, puts the user information to the session, and routes the user to the home page. However, if the username or password is invalid, it forwards the request back to the login page.
public void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String userName = req.getParameter("userName"); String encrypterPassword = req.getParameter ("encrypterPassword"); if (ldapManager.isValidUser(userName, encrypterPassword)) { req.getSession(true).setAttribute("user", userName); req.getRequestDispatcher("home.jsp").forward(req, res); } else { req.setAttribute("error", "Invalid user name or password"); req.getRequestDispatcher("login.jsp").forward(req, res); } }
The process()
method doesn't return any value, but validates user login, and on successful login, it routes the user to the home page. How can we unit test this behavior? We can verify that the isValidUser()
method of LDAPManager
is invoked, then check that the username is put in the session, and confirm that the request is dispatched to the home.jsp
page.
We learned about the mocking object and verifying method invocation using the verify()
method in Chapter 2, Socializing with Mockito. Here, we'll create a mock HttpServletRequest
, HttpServletResponse
, and an LDAPManager
and verify that the actions are taken. We'll stub the isValidUser
method of LDAPManager
to return true
to unit test the successful user login and return false
to unit test the invalid login scenario. The following is the JUnit setup for the LoginController
class:
package com.packt.mockito.advanced.voidmethods; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class LoginControllerTest { private LoginController controller; private @Mock HttpServletRequest request; private @Mock HttpServletResponse response; private @Mock LDAPManager ldapManager; @Before public void beforeEveryTest(){ MockitoAnnotations.initMocks(this); controller = new LoginController(ldapManager); } @Test public void when_valid_user_credentials_for_login_Then_routes_to_home_page(){ } @Test public void when_invalid_user_credentials_Then_routes_to_login_page(){ } }
Mock objects are instantiated by the MockitoAnnotations.initMocks(this)
instance in the beforeEveryTest
method. Two empty test methods are created for unit testing the valid and invalid login, and the sanity checking of the mock objects creation. We'll start with the happy path. Modify the when_valid_user_credentials_for_login_Then_routes_to_home_page()
test, and then we'll modify the when_invalid_user_credentials_Then_routes_to_login_page
test.
After successful login, the process()
method creates a user session, puts the user information to the session, and then dispatches the request. Hence, for this, we need to create a mock HttpSession
object and a RequestDispatcher
object:
@Mock HttpSession session; @Mock RequestDispatcher dispatcher;
We modify the happy path test to verify successful login. Happy path unit tests can verify the most obvious things, such as when a valid user ID and password is passed, the user can login; but when we test complicated business conditions, such as an invalid password or an expired password, we call it the alternate path or sad path. The following is the modified test:
@Test public void when_valid_user_credentials_for_login_Then_routes_to_home_page() throws Exception{ verify(ldapManager).isValidUser(anyString(),anyStrin()); verify(request).getSession(true); verify(session).setAttribute(anyString(), anyString()); verify(request).getRequestDispatcher(eq("home.jsp")); verify(dispatcher).forward(request, response); }
We are verifying a successful login that requires a user session to be created, a session attribute to be set, a request dispatcher object to be created for the home page ("home.jsp"
), and the request dispatcher to be forwarded to the home page. The JUnit test verifies that things are set up and executed sequentially. Similarly, modify the other test to unit test the invalid login. The following is the modified test:
@Test public void when_invalid_user_credentials_Then_routes_to_login_page() throws Exception{ verify(request).getRequestDispatcher(eq("login.jsp")); verify(dispatcher).forward(request, response); }
The following output is shown in the Eclipse JUnit runner when we run the unit tests:
We need to invoke the process()
method and modify the first test to stub LDAPManager
to return true
in order to simulate a successful login. The following is the modified test:
@Test public void when_valid_user_credentials_for_login_Then_routes_to_home_page() throws Exception{ when(ldapManager.isValidUser(anyString(), anyString())).thenReturn(true); when(request.getSession(true)).thenReturn(session); when(request.getRequestDispatcher(anyString())).thenReturn(dispatcher); when(request.getParameter(anyString())).thenReturn("user","pwd"); controller.process(request, response); verify(request).getSession(true); verify(session).setAttribute(anyString(), anyString()); verify(request).getRequestDispatcher(eq("home.jsp")); verify(dispatcher).forward(request, response); }
The isValidUser
method of the ldapManager
is stubbed to return true
, request.getSession()
is stubbed to return a mock HttpSession
object, request.getRequestDispatcher()
is stubbed to return a mock RequestDispatcher
, and finally, the request.getParameter
method is stubbed to return "user"
and then "pwd"
. When we run the tests again, the first test passes! The following is the test output:
We must modify the second test to stub the isValidUser
method to return false
, stub the request.getRequestDispatcher()
to return a mock RequestDispatcher
, and finally, stub the request.getParameter
method to return "user"
and then "pwd"
. The following is the modified test:
@Test public void when_invalid_user_credentials_Then_routes_to _login_page() throws Exception{ when(ldapManager.isValidUser(anyString(), anyString())).thenReturn(false); when(request.getRequestDispatcher(anyString())).thenReturn(dispatcher); when(request.getParameter(anyString())).thenReturn("user","pwd"); controller.process(request, response); verify(request).getRequestDispatcher(eq("login.jsp")); verify(dispatcher).forward(request, response); }
When we run the tests, we get a green bar as shown in the following screenshot:
We learned how to unit test void methods. Revisit the tests; you will find duplicate code in the test methods, such as stubbing the getParameter()
method or stubbing the getRequestDispatcher()
method. You can move the stubbing calls to the beforeEveryTest
method to clean the test code.
The following section explores the concept of exception handling for void methods.
In the preceding example, the LoginController
class calls the LDAPManager
for user validation. The web application fails if the LDAPManager
throws an exception. The DemoController
servlet is the gateway; it should handle any unwanted exceptions and show a proper error message to the user. We have to find a mechanism to handle exceptions.
We'll create a unit test for the DemoController
servlet. To recreate an exceptional condition, we have to stub the LoginController
class to throw an exception, but the problem is the DemoController
constructor. The constructor instantiates the LoginController
class, so we cannot mock the controller. We can refactor the DemoController
constructor to pass a mock instant of the LoginController
class. There are several ways to achieve this; for now, we'll add a constructor to pass the mocked LoginController
class. We cannot remove the default constructor, otherwise the servlet container will fail to instantiate the servlet. Servlets run in a container and the container maintains the servlet's lifecycle. The container invokes the default constructor to instantiate a servlet instance. If we remove the default constructor, the container will fail to create the servlet. The following is the modified code:
@WebServlet("/DemoController") public class DemoController extends HttpServlet { private final LoginController loginController; public DemoController(LoginController loginController) { this.loginController = loginController; } public DemoController() { loginController = new LoginController(new LDAPManagerImpl()) ; } }
The following is the empty unit test for the DemoController
constructor:
public class DemoControllerTest { DemoController controller; @Mock LoginController loginController; @Before public void beforeEveryTest(){ MockitoAnnotations.initMocks(this); controller = new DemoController(loginController); } }
We'll modify the code to handle the exceptions and route the request to an error page. After catching the exception, the servlet will dispatch the request to the error page. So we need to create a mock HttpServletRequest
object, an HttpServletResponse
object, and a RequestDispatcher
object:
@Mock HttpServletRequest request; @Mock HttpServletResponse response; @Mock RequestDispatcher dispatcher;
Add the following test to simulate the scenario:
@Test public void when_subsystem_throws_exception_Then_routes_to_error_page_() throws Exception { verify(request).getRequestDispatcher(eq("error.jsp")); verify(dispatcher).forward(request, response); }
We are verifying the request dispatcher creation for the error.jsp
error page. The LoginController
class needs to throw an exception. The Mockito convention for throwing an exception from a void method is as follows:
doThrow(exception).when(mockObject).someVoidMethod();
We'll modify the test to stub the process()
method in order to throw an exception. The following is the modified test code:
@Test public void when_subsystem_throws_exception_Then_routes_to_error_page_() throws Exception { doThrow(new IllegalStateException("LDAP error")).when(loginController).process(request, response); when(request.getServletPath()).thenReturn("/logon.do"); when(request.getRequestDispatcher(anyString())).thenReturn(dispatcher); controller.doGet(request, response); verify(request).getRequestDispatcher(eq("error.jsp")); verify(dispatcher).forward(request, response); }
When we run the test, it fails for an unhandled exception as exception handling has not been done yet. The following is the JUnit output:
Modify the DemoController
constructor to handle exceptions. The following is the modified code:
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { String urlContext = req.getServletPath(); if (urlContext.equals("/")) { req.getRequestDispatcher("login.jsp").forward(req, res); } else if (urlContext.equals("/logon.do")) { loginController.process(req, res); } else { req.setAttribute("error", "Invalid request path '" + urlContext + "'"); req.getRequestDispatcher("error.jsp").forward(req, res); } } catch (Exception ex) { req.setAttribute("error", ex.getMessage()); req.getRequestDispatcher("error.jsp").forward(req, res); } }
Rerunning the test passes execution; the following is the test output:
An external code dependency may process data in a void method, for example, it may send an e-mail or update a database row. We can easily stub a void method by mocking the dependency, but at times, void methods may change the input argument object's attribute, for example, it may set the error code of an Error
object passed in as an argument, and we may use the modified value in our calculation. In this scenario, if we stub the void method, it doesn't help us to modify or add the stubbed method's argument attribute. As a result, our test might either fail or some portion of the code might remain untested.
Consider the exception handling code for DemoController
. It retrieves the error message of the exception and passes the message to the end users, as the message might not be useful to the business users; it doesn't make any sense to us if we see a NullPointerException
error while booking a movie ticket. Instead of passing the raw business exception to the user, the system should analyze the error message, form a useful error message, and pass a meaningful message to the end user.
We'll modify the DemoController
code to analyze the StackTrace
method, retrieve an error message code for the trace, look up the code for a meaningful error message, and pass the message to the user. We'll create an Error
object with an array of StackTraceElement
and an errorCode
string. The following is the code:
public class Error { private StackTraceElement[] trace; private String errorCode; //Getters and setters are ignored for brevity }
An ErrorHandler
interface takes the Error
object, maps the StackTraceElements
method to an errorCode
string, and sets the code back to the Error
object. The following is the code body:
public interface ErrorHandler { void mapTo(Error error); }
The MessageRepository
interface looks up the error code and retrieves a meaningful message from the database. The following is the MessageRepository
class:
public interface MessageRepository { String lookUp(String... errorCode); }
The following modified DemoController
code invokes the ErrorHandler
and MessageRepository
interface to get a meaningful message, and passes the message to the user.
} catch (Exception ex) { String errorMsg = ex.getMessage(); Error errorDto = new Error(); errorDto.setTrace(ex.getStackTrace()); errorHandler.mapTo(errorDto); if(errorDto.getErrorCode() != null){ errorMsg = messageRepository.lookUp (errorDto.getErrorCode()); } req.setAttribute("error", errorMsg); req.getRequestDispatcher("error.jsp").forward(req, res); }
We ignored the rest of the method and dependencies for brevity. You can download the code for details. The mapTo
method takes an Error
object and populates the errorCode
string of the Error
object. If no matching errorCode
string is found, the errorCode
remains as it is. If the errorCode
string is found, the errorCode
string is passed to messageRepository
for an error message lookup.
When we mock the dependencies (errorHandler
and messageRepository
) and rerun the tests, some portion remains untested. The following is the screenshot of the untested code:
We should modify the Error
object from the void mapTo
method to unit test the untested line. The mapTo
method looks up the database to map a StackTrace
method with an error code, so we must mock out the database call and stub the void
method. The following are reasons behind mocking the database call, and you must configure your tests to adhere to these principles:
However, if we stub the void
method, how can we set the errorCode
string to the Error
object? Also, we cannot directly set the Error
object attributes as the object is created inside the catch block.
The resolution is Mockito's doAnswer()
method. The doAnswer()
method can intercept the void
method call and access the void
method arguments and the mock object. So, we can create our callback Answer
implementation, access the Error
object passed as an argument, and set an errorCode
string to it. The following is the syntax for doAnswer()
:
doAnswer(answer).when(mock).someVoidMethod();
We'll create an anonymous Answer
object , access the Error
object, and set the errorCode
string. The following is the code:
@Test public void when_subsystem_throws_any_exception_Then_finds_error_message_and_routes_to_error_page_() throws Exception { doThrow(new IllegalStateException("LDAP error")).when(loginController).process(request, response); doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Error err = (Error)invocation.getArguments()[0]; err.setErrorCode("123"); return err; } } ).when(errorHandler).mapTo(isA(Error.class)); when(request.getServletPath()).thenReturn("/logon.do"); when(request.getRequestDispatcher(anyString())).thenReturn(dispatcher); controller.doGet(request, response); verify(request).getRequestDispatcher(eq("error.jsp")); verify(dispatcher).forward(request, response); }
The preceding change covers the untested lines. The following screenshot shows the test coverage output:
In this section, we'll explore two methods, namely, doNothing
and doCallRealMethod
.
The doNothing()
method does nothing. By default, when we create a mock object and call a void
method on that mock object, the void
method does not do anything, or rather, it is stubbed by default, but still, we stub void methods using doNothing()
for void method chaining. If you need consecutive calls on a void
method, the first call to throw an error, the next call to do nothing, and then the call after that to perform some logic using doAnswer()
, then follow the ensuing syntax:
doThrow(new RuntimeException()). doNothing(). doAnswer(someAnswer). when(mock).someVoidMethod();
The doCallRealMethod()
method is used when you want to call the real implementation of a method on a mock object. The following is the syntax:
doCallRealMethod().when(mock).someVoidMethod();
The doReturn()
method is like thenReturn()
, but this is used only when when(mock).thenReturn(return)
cannot be used. The when().thenReturn()
method is more readable than doReturn()
. Also, doReturn()
is not type safe. The thenReturn
method checks method return types and raises a compilation error if an unsafe type is passed. You can use doReturn()
when working with spy objects. Here is the syntax for using the doReturn()
test:
doReturn(value).when(mock).method(argument);
The following code snippet provides an example of unsafe usage of doReturn
:
@Test public void when_do_return_is_not_safe() throws Exception { when(request.getServletPath()).thenReturn("/logon.do"); assertEquals("/logon.do", request.getServletPath()); doReturn(1.111d).when(request.getServletPath()); request.getServletPath(); }
The request.getServletPath()
method returns a string value. If we try to stub the request.getServletPath()
method with a double using thenReturn
, the Java compiler will complain about the return type; but if we use doReturn
and return a double value, the test fails at runtime. So doReturn
has two drawbacks; it is unreadable and error prone. The following is the test output:
The following screenshot shows the failure trace:
The doReturn
method becomes handy with spy objects. We'll explore doReturn
in the spy section.
An ArgumentCaptor
object verifies the arguments passed to a stubbed method. Sometimes, we create an object in our code under test and then pass it to a method on a mocked dependency, but never return it. Argument captors let us directly access these values provided to our mocks in order to examine them more closely. An ArgumentCaptor object provides an API to test the computed value.
In our exception handling code, we create an Error
object, set exception trace to the object, invoke the ErrorHandler
interface to map the Error
object to an errorCode
string, and finally, call the MessageRepository
class to return a meaningful error message for the errorCode
string. An ArgumentCaptor
can return to us the argument details passed to a stubbed method.
Mockito verifies argument values in natural Java style by using the equals()
method. This is also the recommended way for matching arguments because it makes tests clean and simple. In some situations though, it is helpful to assert on certain arguments after the actual verification.
An ArgumentCaptor
object is defined as follows:
ArgumentCaptor<T> argCaptor= ArgumentCaptor.forClass(T.class);
Where T
is the type of argument, such as a string or a user-defined class.
The following syntax is used to capture arguments:
verify(mockObject).methodA(argCaptor.capture());
If an ArgumentCaptor
object captures arguments for multiple invocations, the captured values can be retrieved by calling the getAllValues()
method. The getAllValues()
method returns List<T>
and the getValue()
method returns T
, which is the last method invocation result. Here, T
is the type of argument class, such as an integer or any Java class type.
The following code uses an ArgumentCaptor
to verify the argument passed into the lookUp
method.
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(repository).lookUp(captor.capture()); assertEquals("123", captor.getValue());
The following example demonstrates how to capture collection arguments. Create an interface and add a method to accept a list of strings. The following is the code:
interface Service{ void call(List<String> args); }
Try to create an ArgumentCapture
for the list of strings. You cannot create a class for List<String>.class
, so you can try to use List.class
. The following screenshot shows you the Java compilation error while converting List.class
to List<String>
:
The following code snippet creates List.class
and casts it to Class<List<String>>
, and passes it to ArgumentCaptor
. This will give you warnings about unsafe casts; you can suppress the warning by annotating the construct with @SuppressWarnings("unchecked")
:
@Test public void when_captures_collections() throws Exception { Class<List<String>> listClass = (Class<List<String>>)(Class)List.class; ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(listClass); }
The following test provides an example of such a use. Here, service
is a mocked implementation of the Service
interface:
@Test public void when_captures_collections(){ Class<List<String>> listClass = (Class<List<String>>)(Class)List.class; ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(listClass); service.call(Arrays.asList("a","b")); verify(service).call(captor.capture()); assertTrue(captor.getValue(). containsAll(Arrays.asList("a","b"))); }
The following example shows you how to capture an argument of type arrays or var-args (T... t
).
Modify the MessageRepository
class to accept variable arguments of strings as errorCodes
. The following is the modified code:
public interface MessageRepository { String lookUp(String... errorCode); }
Create a test to pass an array to the lookUp
method and capture values. The following is the code snippet:
@Test public void when_capturing_variable_args() throws Exception { String[] errorCodes = {"a","b","c"}; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); repository.lookUp(errorCodes); verify(repository).lookUp(captor.capture(),captor.capture(),captor.capture()); assertTrue(captor.getAllValues().containsAll(Arrays.asList(errorCodes))); }
The following Mockito URL has the fix for the variable argument capture:
https://github.com/mockito/mockito/commit/e43a958833df5aa46f54d7cd83b1c17fa19cc5dc
ArgumentCaptor
is modified in a default branch to capture variable arguments. The following is the code snippet:
verify(messageRepository).lookUp(argumentCaptor.captureVararg());
Mockito facilitates verification if interactions with a mock were performed in a given order using the InOrder
API. It allows us to create an InOrder
of mocks and verify the call order of all the calls of all the mocks.
InOrder
is created with mock object using the following syntax:
InOrder inOrder=inOrder(mock1,mock2,...mockN);
Method invocation order is checked using the following syntax:
inOrder.verify(mock1).methodCall1(); inOrder.verify(mock2).methodCall2();
If methodCall2()
of mock2
is invoked before methodCall1()
of mock1
, the test fails. The following test verifies the test order:
@Test public void when_inorder() throws Exception { request.getServletPath(); service.call(Arrays.asList("a","b")); InOrder inOrder=inOrder(request,service); inOrder.verify(service).call(anyList()); inOrder.verify(request).getServletPath(); }
The test verifies that the call()
method is invoked before the getServletPath()
method, but the methods were invoked in reverse order, so the test will fail. The following screenshot demonstrates the error:
Reordering the verification sequence in the following manner fixes the test:
@Test public void when_inorder() throws Exception { request.getServletPath(); service.call(Arrays.asList("a","b")); InOrder inOrder=inOrder(request,service); inOrder.verify(request).getServletPath(); inOrder.verify(service).call(anyList()); }
A Mockito spy allows us to use real objects instead of mocks by replacing some of the methods with stubbed ones. This behavior allows us to test the legacy code. The spy is useful for legacy code as you cannot invoke a few testing impediment methods from your code under test, and also, you cannot mock a class that needs to be tested. A spy can stub these testing impediments without mocking the code under test. A spy can stub the nontestable methods so that other methods can be tested easily. You can also use spies without doing any stubbing and just use them to verify interactions between two totally real objects.
Once an expectation is set for a method on a spy
object, the spy
object no longer returns the original value. It starts returning the stubbed value, but still exhibits the original behavior for the other methods that are not stubbed.
Mockito can create a spy for a real object. Unlike stubbing, when we use the spy object, real methods are called (unless a method was stubbed).
Spy is also known as partial mock. The following is the declaration of spy
:
SomeClass realObject = new RealImplemenation(); SomeClass spyObject = spy(realObject);
The following is a self-explanatory example of spy
:
@Test public void when_spying_real_objects() throws Exception { Error error = new Error(); error.setErrorCode("Q123"); Error spyError = spy(error); //call real method from spy assertEquals("Q123", spyError.getErrorCode()); //Changing value using spy spyError.setErrorCode(null); //verify spy has the changed value assertEquals(null, spyError.getErrorCode()); //Stubbing method when(spyError.getErrorCode()).thenReturn("E456"); //Changing value using spy spyError.setErrorCode(null); //Stubbed method value E456 is returned NOT NULL assertNotEquals(null, spyError.getErrorCode()); //Stubbed method value E456 assertEquals("E456", spyError.getErrorCode()); }
Spying real objects and calling real methods on a spy
object has side effects; to immunize this side effect, use doReturn()
instead of thenReturn()
.
The following code describes the side effect of spying and calling thenReturn()
:
@Test public void when_doReturn_fails() throws Exception { List<String> list = new ArrayList<String>(); List<String> spy = spy(list); //impossible the real list.get(0) is called and fails //with IndexOutofBoundsException, as the list is empty when(spy.get(0)).thenReturn("not reachable"); }
The spy
object calls a real method when trying to stub get(index)
, and unlike the mock objects, the real method was called and it failed with an ArrayIndexOutOfBounds
error. The following screenshot displays the failure message:
This failure can be protected using doReturn()
, as shown is the following code:
@Test public void when_doReturn_fails() throws Exception { List<String> list = new ArrayList<String>(); List<String> spy = spy(list); //doReturn fixed the issue doReturn("now reachable").when(spy).get(0); assertEquals("now reachable", spy.get(0)); }
We learned that Mockito supports the @Mock
annotation for mocking. Like @Mock
, Mockito offers three useful annotations, namely, @Spy
, @Captor
, and @InjectMocks
:
@Captor
: This simplifies the creation of ArgumentCaptor
, and this is useful when the argument to capture is a horrible generic class@Spy
: This creates the spy of a given object; use it instead of spy(Object)
@InjectMocks
: It injects mock or spy fields into tested objects automatically using constructor injection, setter injection, or field injectionThe following example demonstrates the @captor
annotation:
@RunWith(MockitoJUnitRunner.class) public class AnnotationTest { @Captor ArgumentCaptor<List<String>> captor; @Mock Service service; @Test public void when_captor_annotation_is_used() { service.call(Arrays.asList("a","b")); verify(service).call(captor.capture()); assertTrue(captor.getValue().containsAll(Arrays.asList("a","b"))); } }
The annotation creates the ArgumentCaptor
object, and we don't need to typecast it to Class<List<String>>
.
The following example demonstrates the use of the @spy
annotation:
@RunWith(MockitoJUnitRunner.class) public class SpyAnnotationTest { @Spy ErrorHandlerImpl errorHandler; @Test public void when_spy_annotation_is_used() throws Exception { assertNotNull(errorHandler); } }
A Spy
object of ErrorHandlerImpl
is created automatically for errorHandler
. You cannot create a spy for an interface. The following error message pops up when we try to create a spy for the ErrorHandler
interface:
@Spy ErrorHandler errorHandler;
The following screenshot displays the error message:
The following example demonstrates the use of the @InjectMocks
annotation. Here, we'll create a @spy
annotation and two @mocks
annotations. The @InjectMocks
annotation sets the mocks and spy to the real object as a constructor injection.
@RunWith(MockitoJUnitRunner.class) public class InjectMocksAnnotationTest { @Mock LoginController loginController; @Mock MessageRepository repository; @Spy ErrorHandlerImpl errorHandler; @InjectMocks DemoController controller; @Mock HttpServletRequest request; @Mock HttpServletResponse response; @Mock RequestDispatcher dispatcher; @Test public void when_mocks_are_injected() throws Exception { when(request.getServletPath()).thenReturn("/"); when(request.getRequestDispatcher(anyString())).thenReturn(dispatcher); controller.doGet(request, response); verify(request).getRequestDispatcher(eq("login.jsp")); } }
The DemoController
constructor depends on three classes; the preceding example creates the mock and spy objects and injects them to the DemoController
constructor.
We learned that nonstubbed methods of a mock object return default values, such as Null
for an object and false
for a Boolean. However, Mockito allows us to change the default settings to return other nondefault values; these are basically preconfigured answers. The following are settings that are allowed:
RETURNS_DEFAULTS
: This is the default setting that returns null
for an object, false
for a Boolean, and so on.RETURNS_SMART_NULLS
: This returns smart nulls, which are stubs that act like nulls (in that they throw exceptions if you try and call stub.anyMethod()
), but throw exceptions that are much more useful than normal NullPointerExceptions
by giving you information on which call they came from and where.RETURNS_MOCKS
: This returns mock for objects and default value for primitives. If the object cannot be mocked (such as a final class), a Null
value is returned.RETURNS_DEEP_STUBS
: This returns a deep stub. This is really important for legacy code where we need to stub the method chaining, for example, when Foo calls getBar().getTar().getName()
. Deep stubbing allows Foo to directly stub the getName()
method to return a value. Otherwise, we have to stub Foo's getBar
method to return a mock Bar
object, stub the bar's getTar()
method to return a mock Tar
object, and finally, stub the Tar's getName
method to return a value.CALLS_REAL_METHODS
: This calls the corresponding method from the real implementation of the mocked class.The following example overrides the default Mockito settings and uses different return types. Suppose we have the following classes:
class Foo { Bar bar; //Getter and setter } class Bar { Tar tar; //Getter and setter } class Tar { private String name; //Getter and setter }
The following test case uses the RETURNS_DEFAULTS
setting to return a NULL Bar
object:
@Test public void when_default_settings() throws Exception { Foo fooWithReturnDefault = Mockito.mock(Foo.class, Mockito.RETURNS_DEFAULTS); // default null is returned assertNull(fooWithReturnDefault.getBar()); }
The following test case uses the RETURNS_SMART_NULLS
setting to return a smart NULL
object:
@Test public void when_changing_default_settings_to_return_smartNULLS(){ Foo fooWithSmartNull = Mockito.mock(Foo.class, Mockito.RETURNS_SMART_NULLS); // a smart null is returned assertNotNull(fooWithSmartNull.getBar()); System.out.println("fooWithSmartNull.getBar() =" + fooWithSmartNull.getBar()); }
The following is the System.out
output:
fooWithSmartNull.getBar() =SmartNull returned by this unstubbed method call on a mock:foo.getBar();
The following test case uses the RETURNS_MOCKS
setting to return a mock object hierarchy:
@Test public void when_changing_default_settings_to_return_mocks() { Foo fooWithReturnsMocks = Mockito.mock(Foo.class, Mockito.RETURNS_MOCKS); // a mock is returned Bar mockBar = fooWithReturnsMocks.getBar(); assertNotNull(mockBar); assertNotNull(mockBar.getTar()); assertNotNull(mockBar.getTar().getName()); System.out.println("fooWithReturnsMocks.getBar()=" + mockBar); System.out.println("fooWithReturnsMocks.getBar().getTar().getName()={" + mockBar.getTar().getName()+"}"); }
The RETURNS_MOCKS
setting populates the Foo
object with a mocked Bar
object. A mocked Bar
object has a mocked Tar
object and the mocked Tar
object has an empty mocked string name. The following is the output:
fooWithReturnsMocks.getBar()=Mock for Bar, hashCode: 1620275837 fooWithReturnsMocks.getBar().getTar().getName()={}
The following test case uses the RETURNS_DEEP_STUBS
setting to return a deep-stubbed object hierarchy:
@Test public void when_returns_deep_stub() throws Exception { Foo fooWithDeepStub = Mockito.mock(Foo.class, Mockito.RETURNS_DEEP_STUBS); when(fooWithDeepStub.getBar().getTar().getName()).thenReturn("Deep Stub"); // a deep stubbed mock is returned System.out.println("fooWithDeepStub.getBar().getTar().getName()="+ fooWithDeepStub.getBar().getTar().getName()); assertNotNull(fooWithDeepStub.getBar().getTar().getName()); }
The RETURNS_DEEP_STUBS
setting is very useful for legacy code. In the preceding example, we had to stub the getName()
method of a Tar
object, but to stub the method, we had to mock a series of other objects. Only when we used the RETURNS_DEEP_STUBS
setting could the chaining of the method call stub the method and other objects.
The following is the print output:
fooWithDeepStub.getBar().getTar().getName()=Deep Stub
A static method reset(TâĤ )
enables the resetting of mock objects. The reset()
method clears the stubs.
The following code snippet stubs the getName()
method of a mocked Bar
object. After resetting the getName()
method, the stubbing gets cleared and starts returning the default NULL
value.
@Test public void when_resetting_mocks() throws Exception { Bar bar= Mockito.mock(Bar.class); when(bar.getName()).thenReturn("***"); assertNotNull(bar.getName()); reset(bar); //Bar is reset, the getName() stub is cleared assertNull(bar.getName()); }
Resetting mocks is not recommended as it's a sign that your test is probably doing too much, and you should probably just have another test with fresh mocks instead.
Mockito allows us to create mocks while stubbing it. Basically, it allows you to create a stub in one line of code. This can be helpful to keep the test code clean. For example, some stubs can be created and stubbed during field initialization in a test:
public class InlineStubbing { Bar bar = when(mock(Bar.class).getTar()).thenReturn(new Tar()).getMock(); @Test public void when_stubbing_inline() throws Exception { assertNotNull(bar); assertNotNull(bar.getTar()); } }
The bar
object is stubbed and created at the same time. This is useful when the bar
object is used in many test cases within the test class. The bar
object should always return a Tar
object.
Sometimes, we need to determine whether an object is a mock or a spy. This situation can arise when an object uses the @injectMocks
annotation; it can inject a spy or a mock object. We can find out the type using Mockito.mockingDetails
. It can identify whether a particular object is a mock or a spy.
The following example demonstrates the Mockito.mockingDetails
API.
The ServiceImpl
class has two dependencies, namely, Dependency1
and Dependency2
.
class Dependency1{ } class Dependency2{ }
The following is the ServiceImpl
class:
class ServiceImpl{ private final Dependency1 dependency1; private final Dependency2 dependency2; public ServiceImpl(Dependency1 dependency1, Dependency2 dependency2) { this.dependency1 = dependency1; this.dependency2 = dependency2; } public Dependency1 getDependency1() { return dependency1; } public Dependency2 getDependency2() { return dependency2; } }
The following test demonstrates the usage of mockingDetails
:
import static org.mockito.Mockito.mockingDetails; @RunWith(MockitoJUnitRunner.class) public class MockDetailsTest { @Spy Dependency1 dep; @Mock Dependency1 dep1; @Mock Dependency2 dep2; @InjectMocks ServiceImpl service; @Test public void when_determining_type() throws Exception { assertNotNull(service); assertTrue(mockingDetails(service.getDependency2()).isMock()); assertTrue(mockingDetails(dep).isSpy()); } }
The Service
object can be populated with a stub or a mock Dependency1
. We verified that Dependency2
is a mock and dep1
is a spy. We can also verify service.getDependency1()
to check whether a mock or a stub was injected.
3.149.29.71