Shashi K Kalia
Shashi K Kalia

Reputation: 733

Using @Spy and @Autowired together

I have a Service Class with 3 methods, Service class is also using some @Autowired annotations. Out of 3 methods, I want to mock two methods but use real method for 3rd one.

Problem is:

  1. If I am using @Autowired with @Spy, all three real method implementation is being called.
  2. If I am using @Spy only, call to real method is return with Null pointer as there is no initialisation of Autowired objects.

Upvotes: 57

Views: 57275

Answers (5)

I have a similar problem and we fixed using @SpyBean and @Autowired together:

  @SpyBean
  @Autowired
  ClosedInvoiceEventHandler closedInvoiceEventHandler;

Upvotes: 6

Lucien Evans
Lucien Evans

Reputation: 31

UPDATE 20220509-203459

For TestNg User, Add this to the test class

@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)

and extends AbstractTransactionalTestNGSpringContextTests

then @SpyBean will work.


Two choices are given in this Answer

Howerver, I met problems.

  1. Use @SpyBean annotation from spring-boot-test as the only annotation

This seems a good idea but only for junit users. I am using TestNg in my tests. I have not found a way to make @SpyBean work well with TestNg.

  1. Use Java reflection to "autowire" the spy object, e.g. ReflectionTestUtils

The beans autowired seem all have final methods, because spring have already proxy them and make methods final. So Mockito.spy a autowired bean maybe impossible.

Indeed, i tried and got a exception:

invalid use of argument matchers 0 matchers expected 1 recorded

I didnot find reason myself but i saw explanation from here

So, the only approach left is https://stackoverflow.com/a/55148514/12133207

I am not sure if it does work, I will try. -- tried,not work. maybe because the method parameter is also spring proxied. The methods are alse final.

Upvotes: 0

Jakub Zawadzki
Jakub Zawadzki

Reputation: 61

Using @Spy together with @Autowired works until you want to verify interaction between that spy and a different component that spy is injected into. What I found to work for me was the following approach found at https://dzone.com/articles/how-to-mock-spring-bean-version-2

@Configuration
public class AddressServiceTestConfiguration {
    @Bean
    @Primary
    public AddressService addressServiceSpy(AddressService addressService) {
        return Mockito.spy(addressService);
    }
}

This turns your autowired component into a spy object, which will be used by your service and can be verified in your tests.

Upvotes: 6

yuranos
yuranos

Reputation: 9685

I was surprised myself but it does work for us. We have plenty places like:

@Spy
@Autowired
private FeatureService featureService;

I think I know why you are facing this problem. It's not about injection, it's about when(bloMock.doSomeStuff()).thenReturn(1) vs doReturn(1).when(bloMock).doSomeStuff(). See: http://www.stevenschwenke.de/spyingWithMockito

The very important difference is that the first option will actually call the doSomeStuff()- method while the second will not. Both will cause doSomeStuff() to return the desired 1.

Upvotes: 21

PROrock
PROrock

Reputation: 1200

I know about these two options:

  1. Use @SpyBean annotation from spring-boot-test as the only annotation
@Autowired
@InjectMocks
private ProductController productController;

@SpyBean
private ProductService productServiceSpy;
  1. Use Java reflection to "autowire" the spy object, e.g. ReflectionTestUtils
@Autowired
private ProductController productController;

@Autowired
private ProductService productService;

@Before
public void setUp() {
    ProductService productServiceSpy = Mockito.spy(productService);
    ReflectionTestUtils.setField(productController, "productService", productServiceSpy);
}

Upvotes: 108

Related Questions