Search code examples
javaunit-testingspring-bootjunitmockito

Mockito Mock and Spy in SpringBoot App


I have read quite a few articles/blog/StackOverflow questions, but the confusion regarding Mockito mock and spy still remains. So, I started trying implementing them in a small Spring Boot app. My app has a ProductRepository extending CrudRepository.

Currently I'm testing the repository by mocking ProductRepository as follows

  @RunWith(SpringRunner.class)
  @SpringBootTest(classes = {RepositoryConfiguration.class})
  public class ProductRepositoryMockTest {

    @Mock
    private ProductRepository productRepository;
    @Mock
    private Product product;

    @Test
    public void testMockCreation(){
      assertNotNull(product);
      assertNotNull(productRepository);
    }

    @Test
    public void testSaveProduct() {
      assertThat(product.getId(), is(equalTo(0)));
      when(productRepository.save(product)).thenReturn(product);
      productRepository.save(product);
      //Obviously this will fail as product is not saved to db and hence   
      //@GeneratedValue won't come to play
      //assertThat(product.getId() , is(not(0)));
    }

     @Test
     public void testFindProductById() {

      when(productRepository.findOne(product.getId())).thenReturn(product);
      assertNotNull(productRepository.findOne(product.getId()));
      assertEquals(product, productRepository.findOne(product.getId()));
     }
   }

The test passes. Is this the right way? I also want to understand how to use @Spy here and Why should I need it? Any specific scenarios related to this is most welcome.

Thanks in advance.


Solution

  • I have taken a look at your tests and there are few things to keep in mind (this is based on my experience so the final call is up to you):

    1) Hamcrest - If that is possible i would strongly recommend using Hamcrest for your assert implementations. First of all it is much more versatile and feature rich than the standard junit assertions. Second of all you may one day have the need (as i did on one of my projects) to switch from junit to testng for examle. Having all your assertions based on a xunit-neutral implementation, the switch will not be so painful.

    2) Assertions - Instead of assertNull, assertEquals go for the hamcrest's assertThat(String description, T value, Mathcer<T> matcher); Thanks to that you will get clear error messages when a test breaks.

    3) Small tests - In your Repository test.. do not put all the cases in one test. Try to create many small and simple tests for cases like: findOne.. count.. findAll etc. Again it will be easier to find the problem when a small test like that breaks. And if more cases will come you wont end up with a 200+ lines of a test case which is unacceptable

    4) Naming - Do not name your tests as.. testXYZ. It is obvious that these are test methods. I would recommend using the BDD way of naming: shouldX_whenY_givenZ. F.e. shouldReturnZeroCount_givenNoEntitiesInTheDatabase

    5) Structure - Try to split each of your test implementation in three explicit sections, including comments for best result:

    public void should..() throws Exception{
             // Arrange
    
                // when().then()
                // mock()
    
             // Act
    
                // classUnderTest.process()
    
             // Assert
    
               // assertThat(..)
       }
    

    6) Do not split your test classes between Mock / Spy test. HAve one ImplTest.

    7) Never mock a class which you are testing. In the worst case scenario use Spy if you have to mock some of the methods of the class under test. The point of mocking is to isolate the implementation in the class under test, so that only that classes logic is invoked during the test. Mock only dependencies of the class.