Mockito cannot create Spy of @Autowired Spring-Data Repository

a-kraschitzer picture a-kraschitzer · Aug 9, 2018 · Viewed 7.1k times · Source

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?

Answer

snovelli picture snovelli · Jan 17, 2019

You should use @SpyBean but it's unfortunately broken and doesn't work for spring data Repository

Link to the issue: http://github.com/spring-projects/spring-boot/issues/7033