@RefreshScope 似乎忽略了 Mockito 的模拟

问题描述 投票:0回答:2

我正在使用 Spring Boot 和 Spring Cloud Config 服务实现一个服务来提供配置值。在我的服务中,我有几个配置值,当远程 Git 存储库中的值发生更改时需要刷新,并且我使用

@RefreshScope
来启用该功能。

当我尝试在该服务中注入

RestTemplate
的模拟时,问题就出现了,它似乎忽略它并使用自动装配的实例。如果我注释掉注释,它似乎工作正常。

这是服务的代码:

@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;

    ...


  }

测试源码:

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

    ...
  }
java unit-testing spring-boot mockito spring-cloud
2个回答
2
投票

添加

@RefreshScope
时,bean 将成为代理而不是实际的原始实现。目前,
RestTemplate
是在代理上设置的,而不是在底层实例上设置的。 (如果您调试,您会发现您的
MyServiceImpl
实际上更像是
MyServiceImpl$SpringCgLib#353234
的实例)。

要修复此问题,您需要使用

ReflectionTestUtils
AopTestUtils
手动设置依赖关系。后者是获取实际的代理。

删除

@InjectMocks
注释,并在模拟初始化后将以下内容添加到
setup
方法中:

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

对于 4.2 之前的版本,以下内容可能可以解决问题

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

问题是 Mockito 没有检测代理,只是设置字段。

ReflectionTestUtils
也没有检测到代理,因此需要手动解包。事实上,我之前曾多次踏入这个陷阱,这导致我今天早上创建了 SPR-14050,将其嵌入到
ReflectionTestUtils
中以减轻一点疼痛。


0
投票

我通过禁用 @RefreshScope 魔法来解决这个问题,将代理替换回目标 BeanDefinition。

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 {

    }

}

将其注册为 Spring 的 bean。

@SpringBootTest
@Import({DisableRefreshScope.class})
© www.soinside.com 2019 - 2024. All rights reserved.