【问题标题】:Understanding @MockBean usage With 'RestTemplate restTemplate'使用“RestTemplate restTemplate”了解@MockBean 的用法
【发布时间】:2021-09-23 21:15:23
【问题描述】:

当模拟的restTemplate 调用时,我在第二个测试中看到的行为是 NullPointerException。这指出了重置模拟的问题。令我惊讶的是修复(使两个测试都通过)。

修改代码 @MockBean private RestTemplate restTemplate;@MockBean(reset = MockReset.NONE) private RestTemplate restTemplate; 解决了这个问题。这里有几个问题:

  1. 为什么 MockReset.RESET 的默认 @MockBean 行为不起作用?
  2. 我设置测试的方式是否有问题,导致默认 MockReset.RESET 失败?
  3. 测试配置类有问题吗?

希望我已经提供了足够的上下文来回答这个问题。

我创建了一个简化的示例来说明我所看到的: 测试配置:

@Profile("test")
@Configuration
public class TestConfiguration {

    @Bean
    @Primary
    public ObjectNode getWeatherService(RestTemplate restTemplate) {
        return new WeatherServiceImpl(restTemplate);
    }

}

测试:

@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc
class SamTest {
    @Autowired private MockMvc mockMvc;
    @MockBean private RestTemplate restTemplate;
    /*
    Works:
    @MockBean(reset = MockReset.NONE) private RestTemplate restTemplate;
    Fails:
    @MockBean(reset = MockReset.BEFORE) private RestTemplate restTemplate;
    @MockBean(reset = MockReset.AFTER) private RestTemplate restTemplate;
     */
    @Test
    public void testOne() throws Exception {
        Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
                .thenReturn(new ResponseEntity("{\"weather\" : \"rainy\"}", HttpStatus.OK));

        // Makes call to standard @RestController with a @GetMapping
        // Call to external API is contained in @Service class.
        // Currently controller just passes through the json from the underlying service call.
        this.mockMvc.perform(
                get("/weather/check").
                        contentType(MediaType.APPLICATION_JSON_VALUE)).
                andExpect(status().isOk());
    }

    @Test
    public void testTwo() throws Exception {
        Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
                .thenReturn(new ResponseEntity("{\"error\" : \"bandwidth\"}", HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));

        this.mockMvc.perform(
                get("/weather/check").
                        contentType(MediaType.APPLICATION_JSON_VALUE)).
                andExpect(status().is5xxServerError());
    }
}

服务:

@Service
public class WeatherServiceImpl implements WeatherService {
    private final RestTemplate restTemplate;

    @Autowired
    public WeatherServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public ObjectNode retrieve(URI uri) {
        ResponseEntity<ObjectNode> response = restTemplate.getForEntity(uri, ObjectNode.class);
        return response.getBody();
    }

}

【问题讨论】:

    标签: java spring-boot mockito integration-testing


    【解决方案1】:

    @MockBean默认行为存在误解:

    为什么 MockReset.RESET 的默认 @MockBean 行为不起作用?是 我设置测试的方式有问题,默认 MockReset.RESET 失败了?

    来自MockBean.reset 方法文档:

    应用于模拟 bean 的重置模式。默认是 MockReset.AFTER 意味着模拟在每次之后自动重置 测试方法被调用。

    因此,您的MockBean 将在您的第一个测试用例执行后被重置并从应用程序上下文中注销,然后您的第二个测试用例将发现它为空,而在@MockBean(reset = MockReset.NONE) 的情况下不会像您所做的那样发生。

    【讨论】:

    • 你是对的,我对这种行为有一个基本的误解。我在想这种行为就像是一种测试工具(即 EasyMock),而不是真正理解 Spring MockBean 在做什么。谢谢。
    • @BDF 不客气,从您的问题描述中可以看出这只是一个误会。
    猜你喜欢
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-19
    • 2018-12-20
    • 2013-06-18
    • 1970-01-01
    • 2016-07-07
    相关资源
    最近更新 更多