javaunit-testingspring-bootmockitospring-cloud

@RefreshScope seems to ignore Mockito's mocks


I'm implementing a service using Spring Boot and Spring Cloud Config service to provide the configuration values. In my Service I have a couple of config values which need to refresh when the value changes in the remote Git repo, and I was using @RefreshScope to enable that feature.

The problem comes when I try to inject a mock for RestTemplate in that service, it appears to ignore it and use the autowired instance instead. If I comment out the annotation it seems to work fine.

Here's the code for the Service:

@Service
@RefreshScope
public class MyServiceImpl implements MyService {

    private static final Logger LOG = Logger.getLogger(MyServiceImpl.class);

    @Autowired
    public RestTemplate restTemplate;

    @Value("${opts.default}")
    private String default;

    @Value("${opts.address}")
    private String address;

    @Value("${opts.separator}")
    private String separator;

    ...


  }

Test source code:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ServiceTest {

    @Mock
    private RestTemplate restTemplate;

    @Autowired
    @InjectMocks
    private MyServiceImpl service;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    public void testMethod() throws Exception {
        when(restTemplate.postForObject(anyString(), any(), eq(ServiceResponse.class), anyMap())).thenReturn(getSuccessfulResponse());

        ServiceResponse response = service.doYourStuff();

        Assert.assertNotNull(response);
        Assert.assertTrue(response.isSuccessful());
    }

    ...
  }

Solution

  • When adding the @RefreshScope the bean becomes a proxy instead of an actual raw implementation. Currently the RestTemplate is set on the proxy rather then the underlying instance. (If you debug you would see that your MyServiceImpl is actually more like an instance of MyServiceImpl$SpringCgLib#353234).

    To fix you need to manually set the dependency using ReflectionTestUtils and AopTestUtils. The latter is to obtain the actual proxy.

    Remove the @InjectMocks annotation and add the following to your setup method after the initialization of the mocks:

    Object actualTarget = AopTestUtils.getUltimateTargetObject(service);
    ReflectionTestUtils.setfield(actualTarget, "restTemplate", restTemplate);
    

    For versions earlier as 4.2 the following might do the trick

    Object actualTarget = (service instanceof Advised) ? ((Advised) service).getTargetSource().getTarget() : service;
    

    The problem is that Mockito doesn't detect the proxy and just sets the field. The ReflectionTestUtils doesn't detect the proxy either hence the manual unwrapping. I actually stepped into this trap a couple of times before, which led me to create SPR-14050 this morning to have it embedded in the ReflectionTestUtils to easy the pain a little.