3

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?

ljs
  • 495
  • 1
  • 8
  • 23

2 Answers2

3

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.

  • For problem #2 : In my case, the consumer is actually a spring project with the controller-service structure. And in the unit tests, they use a MockRestServiceServer to invoke the particular method that this URL is going to. `mockServer = MockRestServiceServer.createServer(restTemplate);` and then `mockServer.expect(requestTo(CoreMatchers.containsString(config.getDeviceResource()))) .andExpect(method(HttpMethod.GET);` Is that what you said I should be doing, please? – ljs Sep 26 '17 at 01:49
  • I don't have much context on the spring controller-service structure, but it sounds like that method would be the right thing to call. – Ronald Holshausen Oct 05 '17 at 02:12
1

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>
ertert
  • 71
  • 3
  • You made my day :) It worked like a charm. Just one thing, I didn't have to add the guava dependency- it generated a pact file without the dependency. – ljs Sep 27 '17 at 02:35
  • Yes, you can create a different test for each provider that your service consumes. Or you can append multiple test cases to the createPact Method - if your consumer consumes multiple endpoints from a provider that is. Also adding guava 19 is required if you're using spring cloud. – ertert Oct 04 '17 at 19:29
  • Can I also ask 1) why the pact verification maven plugin( in the provider) always gives a 'success' for me? Doesn't it require the provider to be running for the verification to be a success? 2) In the above JSON body, if I change the values of title, description, date-registered etc, shouldn't the verification fail? – ljs Oct 10 '17 at 19:55
  • Sorry, just now I noticed that the 'Last Verified' column is never updated- its always empty if I go to the pactBroker URL localhost:80 – ljs Oct 11 '17 at 01:24