I noticed that refreshable beans (beans annotated with @RefreshScope
) cannot be spied with @SpyBean
in Spring Boot tests.
Example:
@Component
@RefreshScope
public class RefreshableBean {
public int getValue() {
return 42;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {
@SpyBean
private RefreshableBean refreshableBean;
@Test
public void testGetValueWithSpy() throws Exception {
Mockito.doReturn(23).when(refreshableBean).getValue();
assertThat(refreshableBean.getValue()).isEqualTo(23);
}
}
On Spring Boot 2.0.0 / Spring Cloud Finchley M8 the test fails with
[…] org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock! […]
On Spring Boot 1.5.10 / Spring Cloud Edgware SR2 the test fails with
[…]
Caused by: java.lang.IllegalStateException: Unable to apply Mockito AOP support
at org.springframework.boot.test.mock.mockito.MockitoAopProxyTargetInterceptor.applyTo(MockitoAopProxyTargetInterceptor.java:84)
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:408)
[…]
Caused by: org.mockito.exceptions.misusing.NotAMockException: Argument should be a mock, but is: class RefreshableBean$$EnhancerBySpringCGLIB$$4ec314a5
at org.springframework.boot.test.mock.mockito.MockitoApi$Mockito1Api.mockingProgress(MockitoApi.java:125)
at org.springframework.boot.test.mock.mockito.MockitoAopProxyTargetInterceptor$Verification.<init>(MockitoAopProxyTargetInterceptor.java:95)
at org.springframework.boot.test.mock.mockito.MockitoAopProxyTargetInterceptor.<init>(MockitoAopProxyTargetInterceptor.java:56)
at org.springframework.boot.test.mock.mockito.MockitoAopProxyTargetInterceptor.applyTo(MockitoAopProxyTargetInterceptor.java:80)
[…]
Is this a bug or a known behaviour?
Comment From: spencergibb
If it is a bug, I don't think its a problem with spring cloud, do you? @wilkinsona thoughts?
Comment From: bwaldvogel
Ok, actually I wasn’t sure if it’s a bug and where to file this report. Please let me know where this report belongs to :)
Comment From: wilkinsona
Dealing with Mockito and Spring AOP proxies is quite tricky. See some of the discussion in https://github.com/spring-projects/spring-boot/issues/10352 for example. In this case, I'm not sure that there's anything that Spring Boot or Spring Cloud can do.
Firstly, the way that a spy is being used is a little unusual. Typically, if you want to configure behaviour, I'd expect a mock to be used rather than a spy. That may fix the problem as I think proxy that's used for @RefreshScope
would disappear at that point.
Secondly, it's the test's own code that's responsible for passing the "wrong" thing into Mockito. If you need to continue using a spy or switching to a mock doesn't work as hoped, you may be able to get the right thing to pass into Mockito by using AopProxyUtils to get the underlying target.
Comment From: spencergibb
Thanks @wilkinsona, closing.
Comment From: light0x00
I found a perfect solution to disable @RefreshScope
by replacing the proxy BeanDefinition back to the original target BeanDefinition. After doing so, you can feel free to use the @SpyBean
.
Step1: Copy.
public class DisableRefreshScope implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
String targetBeanName = "scopedTarget." + beanDefinitionName;
if (registry.containsBeanDefinition(targetBeanName)) {
BeanDefinition target = registry.getBeanDefinition(targetBeanName);
registry.removeBeanDefinition(beanDefinitionName);
registry.removeBeanDefinition(targetBeanName);
target.setAutowireCandidate(true);
registry.registerBeanDefinition(beanDefinitionName, target);
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
Step2: Register it as a bean to Spring.
@SpringBootTest
@Import({DisableRefreshScope.class})