I am writing unit test for a method which uses WebClient to call a REST end point. Using MockWebServer, I am able to cover the code for a success response, but I am unable to find any way to simulate error response so that the related code pertaining to error handling also gets covered in unit tests.
source class:
@Service
@Slf4j
public class EmployeeService {
private final WebClient webClient;
private final PaymentServiceConfiguration paymentServiceConfiguration;
public EmployeeService(WebClient webClient, PaymentServiceConfiguration paymentServiceConfiguration){
this.webClient = webClient;
this.paymentServiceConfiguration= paymentServiceConfiguration;
}
public Mono<PaymentDataResponse> getPaymentInfo(PaymentDataRequest paymentDataRequest){
return webClient
.post()
.uri(paymentServiceConfiguration.getBaseUri() + "/payment")
.body(Mono.just(paymentDataRequest, PaymentDataRequest.class)
.retrieve()
.onStatus(HttpStatus::isError, this.handleErrorResponse)
.bodyToMono(PaymentDataResponse.class)
.doOnError((throwable)-> {
log.error("Exception in getPaymentInfo method. Message {}", throwable.getMessage());
throw(Exceptions.propagate(throwable));
});
}
private Mono<? extends Throwable> handleErrorResponse(ClientResponse clientResponse){
Mono<String> errorMessage = clientResponse.bodyToMono(String.class);
return errorMessage.flatMap((message) -> {
log.error("Error response code is: {}, and the message is: {} ", clientResponse.rawStatusCode(), message);
return Mono.error(new RuntimeException(message));
});
}
}
Test Class:
@RunWith(MockitoJunitRunner.class)
public class EmployeeServiceTest {
private MockWebServer server;
private EmployeeService employeeService;
private PaymentServiceConfiguration paymentServiceConfiguration;
private String paymentServiceUri;
@Before
public void setUp() throws IOException{
paymentServiceConfiguration = mock(PaymentServiceConfiguration.class);
paymentServiceUri = "/paymentdomain.com";
employeeService = new EmployeeService(WebClient.create(), paymentServiceConfiguration);
server = new MockWebServer();
server.start();
}
@Test
public testPaymentInfo() throws IOException, InterruptedException {
when(paymentServiceConfiguration.getBaseUri()).thenReturn(server.url(paymentServiceUri).url.toString());
String result = "{\"paymentId\": 101, "\paymentType\": \"online\"}";
server.enque(
new MockResponse.setResponseCode(200)
.setHeader("content-type", "application/json")
.setBody(result);
);
StepVerifier.create(employeeService.getPaymentInfo(new PaymentDataRequest())
.expectNext(new ObjectMapper.readValue(result, PaymentDataResponse.class))
.expectComplete()
.verify();
RecordRequest request = server.takeRequest();
Assert.assertEquals("POST", request.getMethod());
Assert.assertEquals("/paymentdomain.com/payment", request.getPath());
}
@After
public void tearDown() throws IOException {
server.shutdown();
}
}
Above test covers the happy path of code and marks the code coverage appropriately. How can I mock an error here, so that it can cover the below lines in source class code (regarding error scenario)
// .onStatus(HttpStatus::isError, this.handleErrorResponse)
// .doOnError((throwable)-> { ......
You need to provide error status 4xx or 5xxx. For example,
server.enqueue(
new MockResponse().setResponseCode(500)
.setHeader("content-type", "application/json")
.setBody("{}");
);
StepVerifier.create(employeeService.getPaymentInfo(new PaymentDataRequest())
.expectError()
.verify();
ps
I would recommend looking at WireMock which provides a very good API for testing web clients matchig . You could test posittive and negative scanarios including timeouts, retry logic using Scenarios or even simulate timeouts using Delays.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With