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})