1

I'm writing integration tests for an application that gets certain objects from Kafka, validates them, performs various conversions and either saves converted objects to another database or sends a response with errors back to Kafka.

The production database has several named queries that are mapped onto relating methods in the corresponding JpaRepository, like so:

public interface PositionRepository extends JpaRepository<Position, Long> {

  @Procedure(name = "generatePositionCode")
  void generatePositionCode(@Param("clientCode") Integer clientCode);
//remainder omitted

I'm using the Mockito framework and the H2 in-memory database for integration testing. As the test database does not contain such named queries, I want to stub the corresponding methods in the repository and do nothing when they are called (which is ok for testing purposes), but continue calling other non-stubbed methods of the repository.

I've always thought that this is when you should use a spy in Mockito. However, when I define a spy like so:

@Spy
private PositionRespository positionRepository;

public void setUp() {
    doNothing().when(positionRepository).generatePositionCode(anyInt());
}

my integration tests fail with an exception that boils down to:

Caused by: org.h2.jdbc.JdbcSQLException: Database "FT" not found; SQL statement: call FT.Ftposgn.generatePositionCode(?) [90013-197]

If on the other hand, I define a mock with delegation in a configuration class like so:

@Primary
@Bean
public PositionRepository mockPositionRepository(PositionRepository positionRepository) {
    return Mockito.mock(PositionRepository.class, 
                        AdditionalAnswers.delegatesTo(positionRepository));
}

and autowire the repository in the test class:

@Autowired
private PositionRepository positionRepository;

public void setUp() {
    doNothing().when(positionRepository).generatePositionCode(anyInt());
}

all tests are green.

I've been trying to wrap my head around why these two methods produce different results but couldn't. Can anyone please shed some light?

Thanks.

John Allison
  • 966
  • 1
  • 10
  • 30

2 Answers2

2

I managed to solve the problem.

The first issue was that I was using the @Spy annotation instead of the @SpyBean annotation, and thus the spy was not managed by Spring as a bean.

The next pitfall was that Spring Data JPA creates implementations of repositories as CGLIB proxies with final methods, so a Spy cannot be created using just the Mockito core library. You have to add the org.mockito:mockito-inline dependency on the classpath.

The final consideration is that Mockito.verify() and other such methods will fail with a MockitoException because they will be called on the target object instead of the Spy. To overcome this, create a Spy as

@SpyBean(proxyTargetAware = false)

In this case, the calls to verify() will use the proxy directly instead of the target of the AOP advised bean.

John Allison
  • 966
  • 1
  • 10
  • 30
0

In first case you should use @Spy on PositionRepository instead of PersonRepository. If you are testing PositionRepository and wants to mock method of its class then you should make Spy on PositionRepository. If you are testing PositionRepository and wants to mock another class like PersonRepository then you should make Mock on PersonRepository In other words: Spy is part mocking, Mock is full mocking. If you make Spy on object and don't provide when().thenReturn() then it will try to use existing implementation.

Misha Lemko
  • 591
  • 4
  • 9