pact-jvm : how to solve au.com.dius.pact.consumer.PactMismatchesException

8.6k views Asked by At

I am trying to generate a pact between 2 of our services using pact-JVM. But when I try to run the Java class, I get this exception.

1) I suspect something is wrong with the Pact body, is that correct? There is an extra 'message' parameter in the JSON body of the PactDslWithProvider,but in the runTest1() method, I am equating only the lists and when I inspect the results, they are same to me. 2) Is it correct to provide the actual provider URL in the runTest1() method? (the provider is already in place)

au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:
method: GET
path: /devices/v1
query: [externalId:[0942dc67-35de-44f7-a061-743f59436a98]]
headers: [:]
matchers: MatchingRules(rules=[:])
generators: Generators(categories={})
body: OptionalBody(state=MISSING, value=null)

Below is my Java class

public class PactForDevice {
    Map<String, String> headers = MapUtils.putAll(new HashMap<String, String>(), new String[]{"Content-Type", "application/json;charset=UTF-8"});

@Rule
public PactProviderRuleMk2 provider = new PactProviderRuleMk2("device-service-m", this);

@Pact(consumer = "device-r", provider = "device-service-m")
public RequestResponsePact createFragment(PactDslWithProvider builder) {

    return builder
            .given("Device M details")
            .uponReceiving("retrieving Device details")
            .path("/devices/v1")
            .method("GET")
            .query("externalId=0942dc67-35de-44f7-a061-743f59436a98")
            .willRespondWith()
            .headers(headers)
            .status(200)
            .body("{" +
                    "\"data\": [,\n " +
                    "{ \n" +
                    " \"dateRegistered\": \"2017-07-13T11:10:51.000+12:00\",\n" +
                    " \"alias\": \"\",\n" +
                    " \"id\": \"a02b14ee72192ab3\",\n" +
                    " \"description\": \"Samsung SM-G930F\",\n" +
                    " \"title\": \"a02b14ee72192ab3\",\n" +
                    " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                    "},\n" +
                    "{\n" +
                    " \"dateRegistered\": \"2017-07-13T10:45:51.000+12:00\",\n" +
                    " \"alias\": \"\",\n" +
                    " \"id\": \"a41c3af56ec35874\",\n" +
                    " \"description\": \"Samsung SM-T819\",\n" +
                    " \"title\": \"a41c3af56ec35874\",\n" +
                    " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                    " },\n" +
                    " {\n" +
                    " \"dateRegistered\": \"2017-07-13T10:45:31.000+12:00\",\n" +
                    " \"alias\": \"\",\n" +
                    " \"id\": \"bd2b027bbd0a2f17\",\n" +
                    " \"description\": \"Samsung SM-A320Y\",\n" +
                    " \"title\": \"bd2b027bbd0a2f17\",\n" +
                    " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                    " }\n" +
                    "],\n" +
                    " \"message\": \"3 devices found for the user 0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                    "}")
            .toPact();
}

@PactVerification("device-service-m")
@Test
@JsonIgnoreProperties(ignoreUnknown = true)
public void runTest1() throws IOException {

    final GetDevicesResponse deviceResponse = new GetDevicesResponse();

    final List<Device> deviceList = new ArrayList<>();
    Device dev = new Device();
    dev.withDateRegistered("2017-07-13T11:10:51.000+12:00");
    dev.withAlias("");
    dev.withId("a02b14ee72192ab3");
    dev.withDescription("Samsung SM-G930F");
    dev.withTitle("a02b14ee72192ab3");
    dev.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
    deviceList.add(dev);

    Device dev1 = new Device();
    dev1.withDateRegistered("2017-07-13T10:45:51.000+12:00");
    dev1.withAlias("");
    dev1.withId("a41c3af56ec35874");
    dev1.withDescription("Samsung SM-T819");
    dev1.withTitle("a41c3af56ec35874");
    dev1.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
    deviceList.add(dev1);

    Device dev2 = new Device();
    dev2.withDateRegistered("2017-07-13T10:45:31.000+12:00");
    dev2.withAlias("");
    dev2.withId("bd2b027bbd0a2f17");
    dev2.withDescription("Samsung SM-A320Y");
    dev2.withTitle("bd2b027bbd0a2f17");
    dev2.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
    deviceList.add(dev2);

    deviceResponse.setDevices(deviceList);

    final RestTemplate restTemplate = new RestTemplate();
    GetDevicesResponse devices = restTemplate.getForObject("http://localhost:8091/devices/v1?externalId=0942dc67-35de-44f7-a061-743f59436a98", GetDevicesResponse.class);

    assertThat(devices, sameBeanAs(deviceResponse));

}  

}

EDIT:

I just found that if I comment out @Rule part, the test is getting passed - but a pact file is not getting generated. Shouod I explicitly specify a "pact" folder for that?

2

There are 2 answers

4
ertert On BEST ANSWER

I had a similar issue with pacts not being generated after the test would run. I never got them to work using the annotation approach, instead I solved it by extending ConsumerPactTestMk2. Pact will setup the mockserver and mock the response for you.

public class PactForDevice extends ConsumerPactTestMk2 {
    Map<String, String> headers = MapUtils.putAll(new HashMap<String, String>(), new String[]{"Content-Type", "application/json;charset=UTF-8"});

    public RequestResponsePact createPact(PactDslWithProvider builder) {
        return builder
          .given("Device M details")
          .uponReceiving("retrieving Device details")
          .path("/devices/v1")
          .method("GET")
          .query("externalId=0942dc67-35de-44f7-a061-743f59436a98")
          .willRespondWith()
          .headers(headers)
          .status(200)
          .body("{" +
                "\"data\": [,\n " +
                "{ \n" +
                " \"dateRegistered\": \"2017-07-13T11:10:51.000+12:00\",\n" +
                " \"alias\": \"\",\n" +
                " \"id\": \"a02b14ee72192ab3\",\n" +
                " \"description\": \"Samsung SM-G930F\",\n" +
                " \"title\": \"a02b14ee72192ab3\",\n" +
                " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                "},\n" +
                "{\n" +
                " \"dateRegistered\": \"2017-07-13T10:45:51.000+12:00\",\n" +
                " \"alias\": \"\",\n" +
                " \"id\": \"a41c3af56ec35874\",\n" +
                " \"description\": \"Samsung SM-T819\",\n" +
                " \"title\": \"a41c3af56ec35874\",\n" +
                " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                " },\n" +
                " {\n" +
                " \"dateRegistered\": \"2017-07-13T10:45:31.000+12:00\",\n" +
                " \"alias\": \"\",\n" +
                " \"id\": \"bd2b027bbd0a2f17\",\n" +
                " \"description\": \"Samsung SM-A320Y\",\n" +
                " \"title\": \"bd2b027bbd0a2f17\",\n" +
                " \"externalId\": \"0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                " }\n" +
                "],\n" +
                " \"message\": \"3 devices found for the user 0942dc67-35de-44f7-a061-743f59436a98\"\n" +
                "}")
        .toPact();
    }

    @Override
    protected String providerName() {
        return "device-service-m";
    }

    @Override
    protected String consumerName() {
        return "device-r";
    }

    @Override
    protected void runTest(MockServer mockServer) throws IOException {
        final GetDevicesResponse deviceResponse = new GetDevicesResponse();

        final List<Device> deviceList = new ArrayList<>();
        Device dev = new Device();
        dev.withDateRegistered("2017-07-13T11:10:51.000+12:00");
        dev.withAlias("");
        dev.withId("a02b14ee72192ab3");
        dev.withDescription("Samsung SM-G930F");
        dev.withTitle("a02b14ee72192ab3");
        dev.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
        deviceList.add(dev);

        Device dev1 = new Device();
        dev1.withDateRegistered("2017-07-13T10:45:51.000+12:00");
        dev1.withAlias("");
        dev1.withId("a41c3af56ec35874");
        dev1.withDescription("Samsung SM-T819");
        dev1.withTitle("a41c3af56ec35874");
        dev1.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
        deviceList.add(dev1);

        Device dev2 = new Device();
        dev2.withDateRegistered("2017-07-13T10:45:31.000+12:00");
        dev2.withAlias("");
        dev2.withId("bd2b027bbd0a2f17");
        dev2.withDescription("Samsung SM-A320Y");
        dev2.withTitle("bd2b027bbd0a2f17");
        dev2.withExternalId("0942dc67-35de-44f7-a061-743f59436a98");
        deviceList.add(dev2);

        deviceResponse.setDevices(deviceList);

        String url = mockServer.getUrl();
        String path = "devices/v1";
        String query = "externalId=0942dc67-35de-44f7-a061-743f59436a98";

        URIBuilder uriBuilder = null;
        try {
            uriBuilder = new URIBuilder(url)
                                .setPath(path)
                                .setQuery(query);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        GetDevicesResponse devices = new ObjectMapper().readValue(Request.Get(uriBuilder.toString())
                .addHeader("content-type", "application/json")
                .execute().returnContent().asString(), GetDevicesResponse.class);
        assertThat(devices, sameBeanAs(deviceResponse));
    }
}

With this approach I had to add google guava 19 to my pom. But it works good.

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>19.0</version>
</dependency>
2
Ronald Holshausen On

There are a few problems with your test.

Problem #1

You have not specified a port for the Pact provider rule, so it is starting the mock server on a random port. Your test is accessing your provider on port 8091, so Pact is failing the test and reporting that it did not get the expected request, which it did not (the request went to something else listening on port 8091).

You can fix this by either providing the port 8091 to the rule (you'll need to shutdown whatever is running on 8091), or get your client to use the port of the mock server (from calling getMockServer().getPort()).

Problem #2

Your test is using the Spring Rest Template directly, which means it is not really testing anything other than the Spring HTTP client and the bean de-serialisation. You should be using whatever client code you have (i.e. the class that uses the rest template) and call that in the test.