繁体   English   中英

如何在@Service 中模拟 Spring @Autowired WebClient 响应?

[英]How to mock Spring @Autowired WebClient response in @Service?

当使用@Autowired WebClient@Service类检索不同的响应 JSON 时,我想测试程序行为。 为此,我希望在测试中能够用从文件中读取的 JSON 替换从 api url 检索到的响应正文 JSON。

具体来说,我想使用@NotNull@Size注释(当 JSON 无效时)以及在不同(有效)模型时使用@Autowired ModelService的类的行为来测试DTO中完成的验证使用.getModel()方法检索从 JSON 映射的映射。

我的服务如下所示:

@Service
public class ModelServiceImpl implements ModelService {

   @Autowired
   ApiPropertiesConfig apiProperties;

   @Autowired
   private WebClient webClient;

   private static final ModelMapper modelMapper = Mappers.getMapper(ModelMapper.class);

   public Mono<Model> getModel() throws ConfigurationException {
   
       String apiUrl = apiProperties.getApiUrl();

       return webClient.get()
               .uri(apiUrl)
               .accept(MediaType.APPLICATION_JSON)
               .retrieve()
               .bodyToMono(ModelDTO.class)
               .map(modelMapper::modelDTOtoModel);
   }
}

我的 WebClient 定义为:

@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {

   @Bean
   public WebClient getWebClient() {
       HttpClient httpClient = HttpClient.create()
               .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
               .doOnConnected(conn -> conn
                       .addHandlerLast(new ReadTimeoutHandler(10))
                       .addHandlerLast(new WriteTimeoutHandler(10)));

       ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));

       return WebClient.builder()
               .clientConnector(connector)
               .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
               .build();
   }
}

ApiPropertiesConfig 是:

@Configuration
@ConfigurationProperties(prefix = "api")
@Data
@Primary
public class ApiPropertiesConfig {
   private String apiUrl;
}

我已将测试类设置为:

@SpringBootTest
@TestPropertySource(properties = {
       "api.apiUrl=https://url.to.production.model/model.json"
})
@ExtendWith(MockitoExtension.class)
class ApplicationTests {

}

如您所见,当我调用 modelSerice.getModel() 时,webclient 从 api url 检索 json,将其转换为 DTO,然后使用Mapstruct接口映射到 POJO。

我已阅读此处建议的选项: 如何模拟 Spring WebFlux WebClient? ,但是在测试期间,我无法理解如何在服务中将自动装配的 WebClient 替换为模拟的 WebClient 。

由于您使用@SpringBootTest注释,它通过@MockBean SpringExtension模拟 bean 注入应用程序上下文并替换现有的:

@SpringBootTest
@TestPropertySource(properties = {
       "api.apiUrl=https://url.to.production.model/model.json"
})
class ApplicationTests {
    @Mock
    private WebClient.RequestHeadersUriSpec<?> requestHeadersUriMock;
    @Mock
    private WebClient.RequestHeadersSpec<?> requestHeadersMock;
    @Mock
    private WebClient.ResponseSpec responseMock;
    @MockBean
    private WebClient webClientMock;
    private final ModelDTO mockModelDTO = new ModelDTO(.....);

    @Autowired
    private ModelService modelService;


    @Test
    void testModelServiceGetModel() {
        prepareWebClientMock();

        final Model model = modelService.getModel().block();
        assertThat(model).isNotNull();
    }

    private void prepareWebClientMock() {
        doReturn(requestHeadersUriMock).when(webClientMock).get();
        doReturn(requestHeadersMock).when(requestHeadersUriMock).uri(anyString());
        doReturn(requestHeadersMock).when(requestHeadersMock).accept(any());
        doReturn(responseMock).when(requestHeadersMock).retrieve();
        doReturn(Mono.just(mockModelDTO))
                .when(responseMock).bodyToMono(eq(ModelDTO.class));
    }
}

您的问题源于您使用字段注入而不是使用基于构造函数的注入的最佳实践这一事实。 除了前者产生的——正如你已经注意到的——代码难以测试之外,它还有其他的缺点。 例如:

  1. 它不允许创建不可变的组件(即字段不是final
  2. 它可能导致代码违反单一职责原则(因为您可能很容易注入大量可能被忽视的依赖项)
  3. 它将您的代码直接耦合到 DI 容器(即您不能轻松地在 DI 容器之外使用您的代码)
  4. 它可能隐藏注入的依赖项(即可选依赖项和必需依赖项之间没有明确的区别)。

基于所有这些,如果您使用构造函数注入来完成工作会更好。 有了这些,将模拟注入服务可以很容易地完成:


@Mock // The mock we need to inject
private MyMockedService mockedService;

@InjectMocks // The unit we need to test and inject mocks to
private MyUnitToTestServiceImpl unitToTestService;

或者,您可以使用实例化单元直接进行测试,并简单地通过其公共构造函数传入模拟实例。

因此,经验法则是在这种情况下始终使用构造函数注入。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM