[英]Spring Webflux how to Mock response as Mono.error for WebClient Junit
[英]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));
}
}
您的问题源于您使用字段注入而不是使用基于构造函数的注入的最佳实践这一事实。 除了前者产生的——正如你已经注意到的——代码难以测试之外,它还有其他的缺点。 例如:
final
)基于所有这些,如果您使用构造函数注入来完成工作会更好。 有了这些,将模拟注入服务可以很容易地完成:
@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.