Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito cannot create Spy of @Autowired Spring-Data Repository

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?

like image 933
a-kraschitzer Avatar asked Aug 09 '18 09:08

a-kraschitzer


1 Answers

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.

like image 188
snovelli Avatar answered Oct 16 '22 01:10

snovelli