Mockito Mock vs Spy: Key Differences and Best Practices
2 CommentsLast Updated on October 21, 2024 by jt
I have met many developers who refer to tests as “Unit Tests” when they are actually integration tests. In service layers, I’ve seen tests referred to as unit tests, but written with dependencies on the actual service, such as a database, web service, or some message server. Those are part of integration testing. Even if you’re just using the Spring Context to auto-wire dependencies, your test is an integration test. Rather than using the real services, you can use Mockito mocks and spies to keep your tests unit tests and avoid the overhead of running integration tests.
This is not to say Integration tests are bad. There is certainly a role for integration tests. They are a necessity.
But compared to unit tests, integration tests are slow. Very slow. Your typical unit test will execute in a fraction of a second. Even complex unit tests on obsolete hardware will still complete sub-second.
Integration tests, on the other hand, take several seconds to execute. It takes time to start the Spring Context. It takes time to start an H2 in-memory database. It takes time to establish a database connection.
While this may not seem much, it becomes exponential on a large project. As you add more and more tests, the length of your build becomes longer and longer.
No developer wants to break the build. So we run all the tests to be sure. As we code, we’ll be running the full suite of tests multiple times a day. For your own productivity, the suite of tests needs to run quickly.
If you’re writing Integration tests where a unit test would suffice, you’re not only impacting your own personal productivity. You’re impacting the productivity of the whole team.
On a recent client engagement, the development team was very diligent about writing tests. Which is good. But, the team favored writing Integration tests. Frequently, integration tests were used where a Unit test could have been used. The build was getting slower and slower. Because of this, the team started refactoring their tests to use Mockito mocks and spies to avoid the need for integration tests.
They were still testing the same objectives. But Mockito was being used to fill in for the dependency driving the need for the integration test.
For example, Spring Boot makes it easy to test using an H2 in-memory database using JPA and repositories supplied by Spring Data JPA.
But why not use Mockito to provide a mock for your Spring Data JPA repository?
Unit tests should be atomic, lightweight, and fast that is done as isolated units. Additionally, unit tests in Spring should not bring up a Spring Context. I have written about the different types of tests in my earlier Testing Software post.
I have already written a series of posts on JUnit and a post on Testing Spring MVC With Spring Boot 1.4: Part 1. In the latter, I discussed unit testing controllers in a Spring MVC application.
I feel the majority of your tests should be unit tests, not integration tests. If you’re writing your code following the SOLID Principles of OOP, your code is already well structured to accept Mockito mocks.
In this post, I’ll explain how to use Mockito to test the service layer of a Spring Boot MVC application. If Mockito is new for you, I suggest reading my Mocking in Unit Tests With Mockito post first.
Mockito Mocks vs Spies
In a unit test, a test double is a replacement of a dependent component (collaborator) of the object under test. The test double does not have to behave exactly as the collaborator. The purpose is to mimic the collaborator to make the object under test think that it is actually using the collaborator.
Based on the role played during testing, there can be different types of test doubles. In this post, we’re going to look at mocks and spies.
There are some other types of test doubles, such as dummy objects, fake objects, and stubs. If you’re using Spock, one of my favorite tricks was to cast a map of closures in as a test double. (One of the many fun things you can do with Groovy!)
What makes a mock object different from the others is that it has behavior verification. Which means the mock object verifies that it (the mock object) is being used correctly by the object under test. If the verification succeeds, you can conclude the object under test will correctly use the real collaborator.
Spies, on the other hand, provides a way to spy on a real object. With a spy, you can call all the real underlying methods of the object while still tracking every interaction, just as you would with a mock.
Things get a bit different for Mockito mocks vs spies. A Mockito mock allows us to stub a method call. Which means we can stub a method to return a specific object. For example, we can mock a Spring Data JPA repository in a service class to stub a getProduct()
method of the repository to return a Product
object. To run the test, we don’t need the database to be up and running – a pure unit test.
A Mockito spy is a partial mock. We can mock a part of the object by stubbing a few methods, while real method invocations will be used for the other. By saying so, we can conclude that calling a method on a spy will invoke the actual method unless we explicitly stub the method, and therefore the term partial mock.
Let’s look mocks vs spies in action, with a Spring Boot MVC application.
The Application Under Test
Our application contains a single Product
JPA entity. CRUD operations are performed on the entity by ProductRepository
using a CrudRepository
supplied by Spring Data JPA. If you look at the code, you will see all we did was extend the Spring Data JPA CrudRepository
to create our ProductRepository
. Under the hood, Spring Data JPA provides implementations to manage entities for most common operations, such as saving an entity, updating it, deleting it, or finding it by id.
The service layer is developed following the SOLID design principles. We used the “Code to an Interface” technique while leveraging the benefits of dependency injection. We have a ProductService
interface and a ProductServiceImpl
implementation. It is this ProductServiceImpl
class that we will unit test.
Here is the code of ProductServiceImpl
.
ProductServiceImpl.java
package guru.springframework.services; import guru.springframework.domain.Product; import guru.springframework.repositories.ProductRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ProductServiceImpl implements ProductService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private ProductRepository productRepository; @Autowired public void setProductRepository(ProductRepository productRepository) { this.productRepository = productRepository; } @Override public Iterable<Product> listAllProducts() { logger.debug("listAllProducts called"); return productRepository.findAll(); } @Override public Product getProductById(Integer id) { logger.debug("getProductById called"); return productRepository.findOne(id); } @Override public Product saveProduct(Product product) { logger.debug("saveProduct called"); return productRepository.save(product); } @Override public void deleteProduct(Integer id) { logger.debug("deleteProduct called"); productRepository.delete(id); } }
In the ProductServiceImpl
class, you can see that ProductRepository
is @Autowired
in. The repository is used to perform CRUD operations. – a mock candidate to test ProductServiceImpl
.
Testing with Mockito Mocks
Coming to the testing part, let’s take up the getProductById()
method of ProductServiceImpl
. To unit test the functionality of this method, we need to mock the external Product
and ProductRepository
objects. We can do it by either using the Mockito’s mock()
method or through the @Mockito
annotation. We will use the latter option since it is convenient when you have a lot of mocks to inject.
Once we declare a mock` with the @Mockito
annotation, we also need to initialize it. Mock initialization happens before each test method. We have two options – using the JUnit test runner, MockitoJUnitRunner
or MockitoAnnotations.initMocks()
. Both are equivalent solutions.
Finally, you need to provide the mocks to the object under test. You can do it by calling the setProductRepository()
method of ProductServiceImpl
or by using the @InjectMocks
annotation.
The following code creates the Mockito mocks and sets them on the object under test.
. . . private ProductServiceImpl productServiceImpl; @Mock private ProductRepository productRepository; @Mock private Product product; @Before public void setupMock() { MockitoAnnotations.initMocks(this); productServiceImpl=new ProductServiceImpl(); productServiceImpl.setProductRepository(productRepository); } . . .
Note: Since we are using the Spring Boot Test starter dependency, Mockito core automatically is pulled into our project. Therefore no extra dependency declaration is required in our Maven POM.
Once our mocks are ready, we can start stubbing methods on the mock. Stubbing means simulating the behavior of a mock object’s method. We can stub a method on the ProductRepository
mock object by setting up an expectation on the method invocation.
For example, we can stub the findOne()
method of the ProductRepository
mock to return a Product
when called. We then call the method whose functionality we want to test, followed by an assertion, like this.
@Test public void shouldReturnProduct_whenGetProductByIdIsCalled() throws Exception { // Arrange when(productRepository.findOne(5)).thenReturn(product); // Act Product retrievedProduct = productServiceImpl.getProductById(5); // Assert assertThat(retrievedProduct, is(equalTo(product))); }
This approach can be used to test the other methods of ProductServiceImpl
, leaving aside deleteProduct()
that has void
as the return type.
To test the deleteProduct()
, we will stub it to do nothing, then call deleteProduct()
, and finally assert that the delete()
method has indeed been called.
Here is the complete test code for using Mockito mocks:
ProductServiceImplMockTest.java
package guru.springframework.services; import guru.springframework.domain.Product; import guru.springframework.repositories.ProductRepository; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class ProductServiceImplMockTest { private ProductServiceImpl productServiceImpl; @Mock private ProductRepository productRepository; @Mock private Product product; @Before public void setupMock() { MockitoAnnotations.initMocks(this); productServiceImpl=new ProductServiceImpl(); productServiceImpl.setProductRepository(productRepository); } @Test public void shouldReturnProduct_whenGetProductByIdIsCalled() throws Exception { // Arrange when(productRepository.findOne(5)).thenReturn(product); // Act Product retrievedProduct = productServiceImpl.getProductById(5); // Assert assertThat(retrievedProduct, is(equalTo(product))); } @Test public void shouldReturnProduct_whenSaveProductIsCalled() throws Exception { // Arrange when(productRepository.save(product)).thenReturn(product); // Act Product savedProduct = productServiceImpl.saveProduct(product); // Assert assertThat(savedProduct, is(equalTo(product))); } @Test public void shouldCallDeleteMethodOfProductRepository_whenDeleteProductIsCalled() throws Exception { // Arrange doNothing().when(productRepository).delete(5); ProductRepository my = Mockito.mock(ProductRepository.class); // Act productServiceImpl.deleteProduct(5); // Assert verify(productRepository, times(1)).delete(5); } }
Note: An alternative to doNothing()
for stubbing a void
method is to use doReturn(null)
.
Testing with Mockito Spies
We have tested our ProductServiceImpl
with mocks. So why do we need spies at all? Actually, we don’t need one in this use case.
Outside Mockito, partial mocks were present for a long time to allow mocking only part (few methods) of an object. But, partial mocks were considered as code smells. Primarily because if you need to partially mock a class while ignoring the rest of its behavior, then this class is violating the Single Responsibility Principle since the code was likely doing more than one thing.
Until Mockito 1.8, Mockito spies were not producing real partial mocks. However, after many debates & discussions, support for partial mock was added to Mockito 1.8.
You can partially mock objects using spies and the callRealMethod()
method. What it means is without stubbing a method, you can now call the underlying real method of a mock, like this.
when(mock.someMethod()).thenCallRealMethod();
Be careful that the real implementation is ‘safe’ when using thenCallRealMethod()
. The actual implementation needs are able to run in the context of your test.
Another approach for partial mocking is to use a spy. As I mentioned earlier, all method calls on a spy are real calls to the underlying method, unless stubbed. So, you can also use a Mockito spy to partially mock a few stubbed methods.
Here is the code provide a Mockito spy for our ProductServiceImpl.
ProductServiceImplSpyTest.java
package guru.springframework.services; import guru.springframework.domain.Product; import guru.springframework.repositories.ProductRepository; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @RunWith(MockitoJUnitRunner.class) public class ProductServiceImplSpyTest { @Spy private ProductServiceImpl prodServiceSpy; @Mock private ProductRepository productRepository; @Mock private Product product; @Test(expected=NullPointerException.class) public void shouldThrowNullPointerException_whenGetProductByIdIsCalledWithoutContext() throws Exception { //Act Product retrievedProduct = prodServiceSpy.getProductById(5); //Assert assertThat(retrievedProduct, is(equalTo(product))); } public void shouldThrowNullPointerException_whenSaveProductIsCalledWithoutContext() throws Exception { //Arrange Mockito.doReturn(product).when(productRepository).save(product); //Act Product savedProduct = prodServiceSpy.saveProduct(product); //Assert assertThat(savedProduct, is(equalTo(product))); } @Test public void shouldVerifyThatGetProductByIdIsCalled() throws Exception { //Arrange Mockito.doReturn(product).when(prodServiceSpy).getProductById(5); //Act Product retrievedProduct = prodServiceSpy.getProductById(5); //Assert Mockito.verify(prodServiceSpy).getProductById(5); } @Test public void shouldVerifyThatSaveProductIsNotCalled() throws Exception { //Arrange Mockito.doReturn(product).when(prodServiceSpy).getProductById(5); //Act Product retrievedProduct = prodServiceSpy.getProductById(5); //Assert Mockito.verify(prodServiceSpy,never()).saveProduct(product); } }
In this test class, notice we used MockitoJUnitRunner
instead of MockitoAnnotations.initMocks()
for our annotations.
For the first test, we expected NullPointerException
because the getProductById()
call on the spy will invoke the actual getProductById()
method of ProductServiceImpl
, and our repository implementations are not created yet.
In the second test, we are not expecting any exception, as we are stubbing the save()
method of ProductRepository
.
The second and third methods are the relevant use cases of a spy in the context of our application– verifying method invocations.
Conclusion
In Spring Boot applications, by using Mockito, you replace the @Autowired
components in the class you want to test with mock objects. In addition to unit test the service layer, you will be unit testing controllers by injecting mock services. To unit test the DAO layer, you will mock the database APIs. The list is endless – It depends on the type of application you are working on and the object under test. If you’re following the Dependency Inversion Principle and using Dependency Injection, mocking becomes easy.
For partial mocking, use it to test 3rd party APIs and legacy code. You won’t require partial mocks for new, test-driven, and well-designed code that follows the Single Responsibility Principle. Another problem is that when()
style stubbing cannot be used on spies. Also, given a choice between thenCallRealMethod
on mock and spy, use the former as it is lightweight. Using thenCallRealMethod
on mock does not create the actual object instance but bare-bones shell instance of the Class to track interactions. However, if you use to spy, you create an object instance. As regard spy, use it if you only if you want to modify the behavior of small chunk of API and then rely mostly on actual method calls.
The code for this post is available for download here.
Spring and Java Fan
Good explanation, now I understand the difference between Mocks and Spies, thanks!
ROUSSI
Great post. However , you can use @InjectMock instead of injecting productRepository in productServiceImpl on each @Before.