19

I am trying to overlay my whole test environment with Mockito.spy functionality so whenever I want i can stub a method but all other calls go to default functionality. This worked very well with the Service layer but I have problems with the Repository layer.

My setup is as follows:

Mockito - 2.15.0 Spring - 5.0.8 SpringBoot - 2.0.4

Repository:

public interface ARepository extends CrudRepository<ADBO, Long> {}

Service:

@Service
public class AService {

    @Autowired
    ARepository aRepository;

    public ADBO getById(long id) {
        return aRepository.findById(id).orElse(null);
    }

    public Iterable<ADBO> getAll() {
        return aRepository.findAll();
    }
}

The configuration for the spy:

@Profile("enableSpy")
@Configuration
public class SpyConfig {

    @Bean
    @Primary
    public ARepository aRepository() {
        return Mockito.spy(ARepository.class);
    }
}

And my test class:

@ActiveProfiles("enableSpy")
@RunWith(SpringRunner.class)
@SpringBootTest
public class AServiceTest {

    @Autowired
    AService aService;

    @Autowired
    ARepository aRepository;

    @Test
    public void test() {
        ADBO foo = new ADBO();
        foo.setTestValue("bar");
        aRepository.save(foo);

        doReturn(Optional.of(new ADBO())).when(aRepository).findById(1L);
        System.out.println("result (1):" + aService.getById(1));

        System.out.println("result all:" + aService.getAll());

    }
}

Now there are three possible outcomes to this test:

  • aRepository is neither a mock nor a spy:
    org.mockito.exceptions.misusing.NotAMockException: Argument passed to when() is not a mock! Example of corr...
  • aRepository is a mock but not a spy (this is the result I get):
    result (1):ADBO(id=null, testValue=null) result all:[]

  • aRepository is a spy (this is what I want):
    result (1):ADBO(id=null, testValue=null) result all:[ADBO(id=1, testValue=bar)]

I attribute this behavior to the fact that the spring instantiation of the repository is more complex in the background and the repository is not correctly instantiated when calling Mockito.spy(ARepository.class).

I have also tried autowireing the proper instance into the Configuration and calling Mockito.spy() with the @Autowired object.

This results in:

Cannot mock/spy class com.sun.proxy.$Proxy75
Mockito cannot mock/spy because :
 - final class

According to my research Mockito can mock and spy final classes since v2.0.0.

Calling Mockito.mockingDetails(aRepository).isSpy() returns true which leads me to think the object in the background was not correctly instantiated.

Finally my question:

How do I get a spy instance of a Spring-Data Repository in my UnitTest with @Autowired?

a-kraschitzer
  • 191
  • 1
  • 1
  • 4
  • 2
    Why so complex? Annotate the field you want to spy upon with `@SpyBean` instead of trying to shoehorn a dedicated configuration class into place. See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans – M. Deinum Aug 09 '18 at 10:05
  • Using `@SpyBean` instead of `@Autowire` on aRepository results in the same behavior, it becomes mock but not a spy. – a-kraschitzer Aug 09 '18 at 11:36
  • Why go to this effort and complexity to test around a repository when https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html spring boot can help you out? Additionally this requires you to understand the underlying complexities of a repository and how it works. – Darren Forsythe Aug 11 '18 at 12:45
  • 1
    There is an opened bug: https://github.com/spring-projects/spring-boot/issues/7033 – Mihaita Tinta Dec 21 '18 at 13:31
  • did you try this one? https://stackoverflow.com/questions/62698827/spring-aop-aspectj-afterreturning-advice-wrongly-executed-while-mockingbefo/62809074#62809074 – Daniel Pop Jan 18 '22 at 10:34

2 Answers2

11

You should use @SpyBean from spring-boot-test.

This used to be broken (per Spring Boot issue #7033) but it's now been fixed and supported since Spring Boot 2.5.3.

If you still getting this error and can't update your Spring Boot version, you might want to try the work-around proposed by @Mateusz Zając in his answer to this question.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
snovelli
  • 5,804
  • 2
  • 37
  • 50
10

@SpyBean now works with spring data repositories since spring boot version 2.5.3

If you can't upgrade you can work around this by instantiating your spy as such in the test method body:

var yourSpy = Mockito.mock(FooRepository.class, AdditionalAnswers.delegatesTo(real))
Mateusz Zając
  • 126
  • 1
  • 7