Search code examples
javaunit-testingspring-bootmockitojunit4

@Mock jpaRepository calls real save method in other hand @MockBean calls mocked method


I thought I understood difference between @Mock and @MockBean even I thought any object mocked never call real methods, Although when I run below test I can see basket has been inserted on hsqldb logs. So now I feel a bit confused about why basket is inserted when @Mock is used and not inserted when @MockBean is used.

INSERT INTO BASKET VALUES(5,'ABCDEFGHIJ','ACTIVE',1,'2019-01-18 12:00:36.066000','2019-01-18 12:00:36.066000')

In other hand if I do instead then hsqldb are clean. In both cases test is successfull.

@MockBean
private BasketRepository basketRepo;

Test class

@RunWith( SpringRunner.class )
@SpringBootTest( )
@ActiveProfiles( "test" )
public class BasketServiceTests
{

@SpyBean
private BasketService basketService;

@Mock
private BasketRepository basketRepo;

@Autowired
private UserAccountRepository userAccountRepo;

@Test
public void createBasketWithSameOrderRef() throws Exception
{
    UserAccount customer = userAccountRepo.findById( 1 )
            .orElseThrow( () -> new NotFoundException( "Customer not found" ) );

    Basket basket = new Basket();
    basket.setAudit( new Audit() );
    basket.setOrderRef( "ABCDEFGHIJ" );
    basket.setStatus( BasketStatusEnum.ACTIVE );
    basket.setUserAccount( customer );

    when( basketRepo.existsByOrderRef( anyString() ) ).thenReturn( false );
    when( basketRepo.save( isA( Basket.class ) ) ).thenReturn( basket );
    when( basketService.createOrderReference( ) ).thenReturn( "ABCDEFGHIJ" );

    Assert.notNull( basketService.getOrCreateByUserAccountBasket( customer ), "Basket id is null" );

}
}

Service

@Service
public class BasketService 
{
@Autowired
private BasketRepository basketRepo;

public Basket getOrCreateByUserAccountBasket( @NotNull final UserAccount userAccount )
{
    Optional<Basket> optBasket = basketRepo.findByUserAccountAndStatusActive( userAccount );

    if ( optBasket.isPresent() )
    {
        return optBasket.get();
    }

    String orderRef = null;

    do
    {
        orderRef = createOrderReference();
    }
    while( basketRepo.existsByOrderRef( orderRef ) );

    Basket basket = new Basket();
    basket.setAudit( new Audit() );
    basket.setOrderRef( orderRef );
    basket.setStatus( BasketStatusEnum.ACTIVE );
    basket.setUserAccount( userAccount );

    return basketRepo.save( basket );
}

public String createOrderReference()
{
    return RandomStringUtils.random( 10, true, false ).toUpperCase();
}
}

Solution

  • @MockBean is a Spring annotation and is the one that should be used in integration tests in order to replace real bean with a mocked one:

    Annotation that can be used to add mocks to a Spring ApplicationContext.

    Mockitos @Mock creates a mock of that repository but does not inject that to the BasketService.

    If you really need to used the Mockitos mocked version you would have to do it manually in the test:

    @Mock
    private BasketRepository basketRepo;
    
    @Test
    public void createBasketWithSameOrderRef() throws Exception
    {
       basketService.setBasketRepository(basketRepo);
       ...
    

    I wrote an article on Mockito Stubbing if you need a further read.