Pact: Solving PactMismatchesException - java

I'm trying to setup a contract test with Pact (so far only Consumer-side).
This is what my code looks like:
#Pact(consumer = "Consumer")
RequestResponsePact apiIsReachablePact(PactDslWithProvider builder) {
return builder.given("api is reachable")
.uponReceiving("load api")
.method("GET")
.path("/?format=json")
.willRespondWith()
.status(200)
.headers(headers())
.body(newJsonBody(object -> {
object.stringType("ip", "XYZ");
}).build())
.toPact();
}
#Test
#PactTestFor(pactMethod = "apiIsReachablePact")
public void apiIsReachable() throws IOException {
//Given
HttpUriRequest request = new HttpGet("https://api.ipify.org");
//When
HttpResponse httpResponse = HttpClientBuilder.create().build().execute(request);
//Then
assertEquals(httpResponse.getStatusLine().getStatusCode(), HttpStatus.SC_OK);
}
I tried to make it as simple as possible, but I receive the following error:
au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:
method: GET
path: /?format=json
query: {}
headers: {}
matchers: MatchingRules(rules={})
generators: Generators(categories={})
body: MISSING
...
Could anyone please help me along here?

Pact doesn't intercept your requests, so this call doesn't actually talk to the Pact mock server, hence why it was not received - it's going to the real API:
HttpUriRequest request = new HttpGet("https://api.ipify.org");
You don't test against real API in Pact in the consumer test, you mock out the provider and test using the Pact Mock. It will then generate a contract that the provider can then use to verify your expectations:
It should be:
#Test
#PactTestFor(pactMethod = "apiIsReachablePact")
public void apiIsReachable(MockServer mockServer) throws IOException {
//Given
HttpUriRequest request = new HttpGet(mockServer.getUrl());
//When
HttpResponse httpResponse = HttpClientBuilder.create().build().execute(request);
//Then
assertEquals(httpResponse.getStatusLine().getStatusCode(), HttpStatus.SC_OK);
}
See this example and this workshop for more.

Related

au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:

This test case is for mocking the health check contract.
TestClass
#Pact(consumer = "Consumer")
public RequestResponsePact getHealthCheck(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "text/plain");
headers.put("callchainid", "a4275861-f60a-44ab-85a6-c0c2c9df5e27");
return builder
.given("get health check")
.uponReceiving("get health data")
.path("/health")
.method("GET")
.headers(headers )
.willRespondWith()
.status(200)
.body("{\"status\":\"UP\",\"components\":{\"db\":{\"status\":\"UP\",\"details\":{\"database\":\"PostgreSQL\",\"validationQuery\":\"isValid()\"}}}}")
.toPact();
}
#Test
#PactTestFor(pactMethod = "getHealthCheck")
void getHealthData(MockServer mockServer) {
WebClient webClient=WebClient.builder().baseUrl(mockServer.getUrl()).build();
final String callChainId="a4275861-f60a-44ab-85a6-c0c2c9df5e27";
ThreadContext.put(CallChainIdService.HEADER_NAME, callChainId);
AsyncClient asyncClient=new AsyncClient(webClient);
Mono<ClientResponse> responseMono=asyncClient.getHealthCheck();
System.out.println(responseMono);
}
Here the webclient end point code which i am trying to hit,
AsyncClient Class
private final WebClient CLIENT;
#Override
public Mono<ClientResponse> getHealthCheck() {
return get(MediaType.TEXT_PLAIN, "/health");
}
private Mono<ClientResponse> get(MediaType contentType, String uri, Object... params) {
return CLIENT
.mutate()
.defaultHeader(CallChainIdService.HEADER_NAME, ThreadContext.get(CallChainIdService.HEADER_NAME))
.build()
.get()
.uri(uri, params)
.accept(contentType)
.exchange();
}
When i run the test , i got PactMismatchesException: The following requests were not received.
au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:
method: GET
path: /health
query: {}
headers: {callchainid=[a4275861-f60a-44ab-85a6-c0c2c9df5e27], Content-Type=[text/plain]}
matchers: MatchingRules(rules={})
generators: Generators(categories={})
body: MISSING
I am not sure what i am doing wrong here. Appreciate your inputs and help
It looks like you have content-type as the expected header you send in the GET call, but in fact you send only an accept header (both with the media type text/plain).
I think your test should be updated to use accept.

Test ExceptionHandler with RestTemplate

I have a method that makes a hit to external API and I have the exception handler is written to handle the errors and send the client-friendly response in case of errors. I have a requirement to test the non 200 OK responses from that external API such as Bad Request, Internal Server Error, and assert that the exception handler method should be invoked to send a client-friendly message. I am able to successfully mock the response of external API as Bad Request but it is not throwing the HttpStatusCodeException which is ideally thrown for 4xx status code and how can I verify method invocation of exception handler
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
private final HttpHeaders httpHeaders = new HttpHeaders();
private final NotificationServiceImpl notificationService = new NotificationServiceImpl(restTemplate, httpHeaders, NOTIFICATION_API_URL, PRIMARY_NOTIFIERS, CC_NOTIFIERS, LANG, APPLICATION_NAME);
#Autowired
private ExceptionTranslator exceptionTranslator;
#Test
void testErrorOnSendNotification() {
Map<String, Instant> messages = Map.of("sample message", Instant.now());
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
// assertThrows(HttpStatusCodeException.class, () -> notificationService.sendNotification(messages));
verify(exceptionTranslator, times(1)).handleExceptions(any(), any());
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Problem> handleExceptions(NativeWebRequest request, Exception error) {
Problem problem =
Problem.builder()
.withStatus(Status.BAD_REQUEST)
.withTitle(error.getMessage())
.withDetail(ExceptionUtils.getRootCauseMessage(error))
.build();
return create(error, problem, request);
}
You are mocking the restTemplate response. The actual #ExceptionHandler is not called at all. You are bypassing that layer.
In your case, in order to verify the ExceptionHandler, your service layer can be mocked, but the actual REST call has to proceed through, and a REAL response has to be triggered, in order for you to verify the Response Status Code + message.
Psuedo Code below:
#Service
class Service{
public void doSomeBusinessLogic() throws SomeException;
}
#RestController
class ControllerUsingService{
#AutoWired
private Service service;
#POST
public Response somePostMethidUsingService() throws SomeException{
service.doSomeBusinessLogic(someString);
}
}
#Test
void testErrorOnSendNotification() {
when(service.doSomeBusinessLogic(anyString()))
.thenThrow(SomeExceptionException.class);
Response receivedResponse = restTemplate.post(request, headers, etc);
//assert receivedResponse status code + message.
}
Hope that makes sense,
For further clarification:
By doing:
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
You are bypassing service layer and actually stating that whenever I make a request towards /API/xyz, then I should receive a BAD_REQUEST. That means whatever exception handling you have is going to be bypassed.

ServerResponseFilter not being applied to GET

I have a Quarkus application with the following filters definition:
#ApplicationScoped
#Slf4j
public class Filters {
// some #Inject parameters i'm using
#ServerRequestFilter(preMatching = true)
public void requestLoggingFilter(ContainerRequestContext requestContext) {
log.info("Recv: [{}] {}, {}", requestContext.getHeaderString("myHeader"), requestContext.getMethod(), requestContext.getUriInfo().getRequestUri());
}
#ServerResponseFilter
public void responseBasicHeaderFilter(ContainerResponseContext responseContext) {
responseContext.getHeaders().putSingle("myHeader, "myValue");
}
#ServerResponseFilter
public void responseLoggingFilter(ContainerResponseContext responseContext) {
log.info("Sent: [{}] {} {}", responseContext.getHeaderString("myHeader"), , responseContext.getStatusInfo(), responseContext.getEntity());
}
}
And I have two tests:
Test Class config:
#QuarkusTest
public class MyTest {
...
}
Test A:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
final Response response2 = given()
.get(BASE_URL)
.then()
.extract().response();
assertEquals(200, response2.getStatusCode(), () -> "Got: " + response2.prettyPrint());
assertEquals("myValue", response2.getHeader("myHeader"));
Test B:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
If i run Test B on it's own, it passes.
If i run Test A however the last assertion fails (the header value is not there).
The #ServerResponseFilter seem to not be called beyond the first time, however #ServerRequestFilter seem to be fine.
I have tested the api manually and can confirm the same behaviour. Calling the GET request first will also have the same behaviour.
I have verified that the response generated by my Controller (pojo) is generated successfully.
What could be preventing it from being rerun?
Turns out it wasn't related to GET vs POST
my GET method was returning a Multi . I converted this to Uni> and it worked.
From the documentation i found this snippet
Reactive developers may wonder why we can't return a stream of fruits directly. It tends to eb a bad idea with a database....
The keyword being we can't so I imagine this is not supported functionality

Testing REST Api mock http server

I would like to test an application that connects to Github api, download some records and do something with them.
I want to have a mock object and I did something like that:
#SpringBootTest
public class GithubApiTest
{
GithubApiClient githubApiClient;
#Mock
HttpClient httpClient;
HttpRequest httpRequest;
#Value("response.json")
private String response;
#BeforeTestClass
void init() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://api.github.com/repos/owner/reponame"))
.build();
githubApiClient = new GithubApiClient(httpClient);
Mockito.when(httpClient.send(request, HttpResponse.BodyHandlers.ofString())).thenReturn(<here>
);
githubApiClient = new GithubApiClient(httpClient);
}
}
Looks it good? What should I put into thenReturn (it needs a HttpResponse but I dont know how to create this). Thanks for your answers. If you have any better ideas I will be grateful.
Update:
String response is a example reponse
You create a mocked response of type HttpResponse.
The mockedResponse will have status code 200, and the response body is "ExampleOfAResponseBody" which is of type String which you requested.
import java.net.http.HttpResponse;
HttpResponse<String> mockedResponse = Mockito.mock(HttpResponse.class);
Mockito.when(mockedResponse.statusCode()).thenReturn(200);
Mockito.when(mockedResponse.body()).thenReturn("ExampleOfAResponseBody");
Mockito.when(httpClient.send(request, HttpResponse.BodyHandlers.ofString())).thenReturn(mockedResponse);

Testing Okhttp3 response body - java.lang.IllegalStateException: request == null

I have a method in class which does a HTTP GET call to get the response object and utilize this object further. The pesudo code is below:
public class ABC{
public method abc1(){
HttpUrl url = HttpUrl.parse("url").newBuilder()
.addPathSegment("path1")
.build();
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
ResponseBody responseBody = response.body();
String body = responseBody.string();
//other logic
}catch (IOException e) {}
}
}
Now I am writing a unit test to test with different values in the response object (json object). This is as below:
public class ABCTest{
#Mock
private OkHttpClient mockHttpClient;
#Mock
private Call mockCall;
#Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
#Test
public void abc1Test(){
ResponseObjectInJson responseObjectInJson = new ResponseObjectInJson(); //this is a object from my POJO class that i create in order to be received as a response
JSONObject jsonObject = new
JSONObject(responseObjectInJson);
ResponseBody body =
ResponseBody.create(MediaType.parse("application/json"),new
Gson().toJson(jsonObject));
Response.Builder builder = new Response.Builder();
builder.code(200);
Response response = builder.body(body).build();
when(mockCall.execute()).thenReturn(response);
when(mockHttpClient.newCall(any(Request.class))).thenReturn(mockCall);
//call the abc1() method here to see the response and behaviour
}
}
The problem is, when i debug, it throws InvocationTargetException when building the response builder.body(body).build();
And shows java.lang.IllegalStateException: request == null. I understand that i need to set request in the Response.Builder because when i evaluate the expression builder.body(body) in debugger, in the result it shows headers and body, but request is null.
i.e., builder.request(//a request here)
My question is:
1. In response why the request is needed?
2. How to set this? because i am unable to mock since its final.
Thanks in advance

Categories

Resources