当前位置: 首页 > 知识库问答 >
问题:

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

公良渝
2023-03-14

我想测试使用自动连线网络客户端的服务类检索不同响应JSON时的程序行为。为此,我希望在测试中能够用从文件读取的JSON替换从api url检索的响应体JSON。

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

我的服务是这样的:

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

我的网络客户端定义为:

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


共有2个答案

贡念
2023-03-14

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

  1. 它不允许创建不可变组件(即字段不是final)
  2. 这可能会导致html" target="_blank">代码违反单一责任原则(因为您可能很容易注入大量的依赖项,这些依赖项可能会被忽略)
  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;

或者,您可以直接使用实例化要测试的单元,只需通过其公共构造函数传入模拟实例。

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

储国发
2023-03-14

由于您使用了SpringBootTest注释,该注释通过SpringExtension增强了您的测试,因此您可以使用MockBean将模拟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));
    }
}
 类似资料:
  • 我们编写了一个小型Spring Boot REST应用程序,它在另一个RESTendpoint上执行REST请求。 我们是Spring新手,在为这个小代码段编写单元测试时遇到了困难。 是否有一种优雅的(反应式)方式来模拟webClient本身或启动webClient可以用作endpoint的模拟服务器?

  • 我有一个Spring组件我想测试,这个组件有一个autowired属性,为了单元测试的目的,我需要更改它。问题是,这个类在后构造方法中使用autowired组件,所以我不能在它实际使用之前替换它(即通过反射)。 我该怎么做? 在调用postconstruct方法之前,有什么方法可以用其他东西替换资源吗?喜欢告诉Spring JUnit runner autowire不同的实例吗?

  • 我试图模拟Spring WebClient,并且在WebClient.builder()模拟中遇到问题。到目前为止,我在测试中定义的模拟没有被使用,我想是因为这个构建器没有返回我的模拟WebClient。如何让构建器返回我的模拟? WebClient的用途如下: 当我遵循这些解决方案时,我没有看到模拟有任何问题:如何模拟Spring WebFlux WebClient?但是模拟没有被使用。我如何模

  • 我使用的是Spring3.1.4.Release和Mockito1.9.5。在我的春季课上,我有: 我想为我的“Defaulturl”字段模拟一个值。请注意,我不想模拟其他字段的值--我希望保持这些字段的原样,只保留“Defaulturl”字段。还要注意,我的类中没有显式的“setter”方法(例如),我不想仅仅为了测试的目的创建任何方法。 既然如此,我如何模拟一个字段的值呢?

  • 我正在从事Spring WebFlux项目, 我正在调用第三方API以使用WebClient创建实体。 如果WebClient得到4xx作为响应代码,我想保留错误。 下面是请求第三方API的代码 API。Java语言 如果服务器返回4xx响应,那么我只是将其转换为特定的错误响应。 调用方法如下 APIService。Java语言 要求Java语言 Response.java 负责人。Java语言

  • 我正在为一个使用 WebClient 调用 REST endpoint的方法编写单元测试。使用MockWebServer,我能够涵盖成功响应的代码,但是我找不到任何方法来模拟错误响应,以便与错误处理相关的代码也包含在单元测试中。 源类: 测试类: 上面的测试涵盖了代码的快乐路径,并适当地标记了代码覆盖率。我如何在这里模拟错误,以便它可以覆盖源代码中的以下行(关于错误场景) // .onStatus