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());
}
...
}
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.