Transcripted Summary

In this chapter we will create our provider verifier and add another example to your contract.

Provider Verifier

Navigate to your provider folder and create a new file called ClientsProvider.spec.js. Remember that in a real world scenario this would be defined in the provider project only.




const path = require("path")
const { Verifier } = require("@pact-foundation/pact")
const { server, importData } = require("../../../src/provider")
const SERVER_URL = "http://localhost:8081"

server.listen(8081, () => {
   importData()
   console.log(`Clients Service listening on ${SERVER_URL}`)
 })

  describe("Clients Service Verification", () => {
   it("validates the expectations of Client Service", () => {
 	let opts = {
       	provider: "Clients Service",
       	logLevel: "DEBUG",
       	providerBaseUrl: SERVER_URL,
       	pactUrls: [
             	path.resolve(
               	process.cwd(),
               	"./__tests__/contract/pacts/frontend-clientsservice.json"
             	)
           	],
       	consumerVersionTags: ["dev"],
       	providerVersionTags: ["dev"],
       	publishVerificationResult: false,
       	providerVersion: "1.0.0"
     	}

   	return new Verifier(opts).verifyProvider().then(output => {
       	console.log("Pact Verification Complete!")
       	console.log(output)
   	})
   })
})

Libraries are required, so add the following line:


const path = require("path")

The next line gets the contracts from the pact broker and verifies whether or not the contract is following expectations. I’ll elaborate more on this later.


const { Verifier } = require("@pact-foundation/pact")

This line imports the server as well as data. We can create the server with the existing JSON file.


const { server, importData } = require("../../../src/provider")

Remember that the server does not require importing anything related to the consumer. We simply need to run the real server and then run the contracts against it.


const SERVER_URL = "http://localhost:8081"

Here we will listen to the server on the specified port, then import and inject the data.


server.listen(8081, () => {
   importData()
   console.log(`Clients Service listening on ${SERVER_URL}`)
 })

Here we are testing with real dependencies. You test against the actual service but use mocks to avoid issues associated with End-to-End testing. Subsequently, tests are representative of real world application workflows and contracts.

We need to create the test itself so let’s add a describe() call with the client service verification and validate expectations of the client service:



describe("Clients Service Verification", () => {
   it("validates the expectations of Client Service", () => {

   })
})

We need to send the options. provider will be the name of the provider. logLevel is the log level. providerBaseUrl is the server URL. pactUrls is the Pact location. In this example we are saving Pacts to a folder but if you have a Pact broker that publishes contracts the provider can point to that instead. Specify the name of the contract that was generated by the consumer (frontend-clientsservice.json). The first name is the name of the consumer used during Pact setup while the second name is the name of the provider that was also set up.


pactUrls: [path.resolve(process.cwd(), "./__tests__/contract/pacts/frontend-clientsservice.json")]

Next, we add version tags. In this case we will use “dev”. Alternatively providerVersion can be retrieved from the Package.json to avoid hard-coding it here.


consumerVersionTags: ["dev"],
providerVersionTags: ["dev"],
publishVerificationResult: false,
providerVersion: "1.0.0"

Finally, return the verifier. Create a new instance, pass the options as an argument and then call the verifyProvider() function. This is going to verify the contract and check if the provider is following the expectations stated in the contract.


return new Verifier(opts).verifyProvider().then(output => {

})

The best of both worlds is employed here. We are still using mocks but with the benefits of a fully deployed, end-to-end application flow with fewer redundancies in environment setup. The resulting tests are fast and deterministic.

Now we need to run this verifier against our contracts. Navigate to your terminal and execute the following command:


npm run test:provider



We observe that the expectations of the client service are successfully validated.


POST Client Test

Let’s create another example. Return to clientConsumer.spec.js. Following the `GET Clients” test let’s create a “POST Client” test.


"use strict"

const { Matchers } = require("@pact-foundation/pact")
const { getClients, postClient } = require("../../../src/consumer")

describe("Clients Service", () => {
   const GET_EXPECTED_BODY = [{
   	"firstName": "Lisa",
   	"lastName": "Simpson",
   	"age": 8,
   	"id": 1
   },
   {
   	"firstName": "Wonder",
   	"lastName": "Woman",
   	"age": 30,
   	"id": 2
   },
   {
   	"firstName": "Homer",
   	"lastName": "Simpson",
   	"age": 39,
   	"id": 3
   }]

   afterEach(() => provider.verify())

   describe("GET Clients", () => {
   	beforeEach(() => {
       	const interaction = {
           	state: "i have a list of clients",
           	uponReceiving: "a request for all clients",
           	withRequest: {
               	method: "GET",
               	path: "/clients",
               	headers: {
                   	Accept: "application/json, text/plain, */*",
               	},
           	},
           	willRespondWith: {
               	status: 200,
               	headers: {
                   	"Content-Type": "application/json; charset=utf-8",
               	},
               	body: GET_EXPECTED_BODY,
           	},
       	}
       	return provider.addInteraction(interaction)
   	})

   	test("returns correct body, header and statusCode", async() => {
       	const response = await getClients()
       	expect(response.headers['content-type']).toBe("application/json; charset=utf-8")
       	expect(response.data).toEqual(GET_EXPECTED_BODY)
       	expect(response.status).toEqual(200)
   	})
   })

   const POST_BODY = {
   	firstName: "Rafaela",
   	lastName: "Azevedo",
   	age: 29
   }

   const POST_EXPECTED_BODY = {
   	firstName: POST_BODY.firstName,
   	lastName: POST_BODY.lastName,
   	age: POST_BODY.age,
   	id: 3
   }

   describe("POST Client", () => {
   	beforeEach(() => {
       	const interaction = {
           	state: "i create a new client",
           	uponReceiving: "a request to create client with firstname and lastname",
           	withRequest: {
               	method: "POST",
               	path: "/clients",
               	headers: {
                   	"Content-Type": "application/json;charset=utf-8"
               	},
               	body: POST_BODY,
           	},
           	willRespondWith: {
               	status: 200,
               	body: Matchers.like(POST_EXPECTED_BODY).contents,
           	},
       	}
       	return provider.addInteraction(interaction)
   	})
   	test("returns correct body, header and statusCode", async() => {
       	const response = await postClient(POST_BODY)
       	console.log(response.data)
       	expect(response.data.id).toEqual(3)
       	expect(response.status).toEqual(200)
   	})
   })
})

Create the constant POST_BODY and then define firstName, lastName and age.


const POST_BODY = {
    firstName: "Rafaela",
    lastName: "Azevedo",
    age: 29
}

This time we are going to use matchers by adding them to POST_EXPECTED_BODY. Generally you want to use exact matching when defining the expectations for a request since we control the data at that stage. Request matching does not allow unexpected values to be present in the JSON request body or query strings.


const POST_EXPECTED_BODY = {
    firstName: POST_BODY.firstName,
    lastName: POST_BODY.lastName,
    age: POST_BODY.age,
    id: 3
}

Response matching should be designed to be as accommodating as possible. On the POST_EXPECTED_BODY constant we are getting the same values from the post body and we will use matchers inside of the response.

This test follows the same general structure as the “GET clients” test. Note the differences in the withRequest method value as well as headers:


describe("POST Client", () => {

   	beforeEach(() => {
       	const interaction = {
           	state: "i create a new client",
           	uponReceiving: "a request to create client with firstname and lastname",
           	withRequest: {
               	method: "POST",
               	path: "/clients",
               	headers: {
                   	"Content-Type": "application/json;charset=utf-8"
               	},
               	body: POST_BODY,
           	},
           	willRespondWith: {
               	status: 200,
               	body: Matchers.like(POST_EXPECTED_BODY).contents,
           	},

As with the previous test, Postman can be used for verification. Run the provider server, then run the post request to verify whether or not the correct headers are being sent.

In the request we send POST_BODY which we define and in the response we will use matchers. As previously mentioned, matchers can be written to be flexible when matching expectations. Matching will occur against the value of contents.

NOTE

Matchers.like() will allow any field so long as the type is the same.


Now define the test:


test("returns correct body, header and statusCode", async() => {
    const response = await postClient(POST_BODY)
    console.log(response.data)
    expect(response.data.id).toEqual(3)
    expect(response.status).toEqual(200)
})

Again, we need to exercise the same function that the consumer invokes from the server by passing POST_BODY. The response is received and expectations will be validated against the response. This scenario will check if the correct ID is returned from the response. Pass response.data.id to expect() and check if the value is equal to three. This will be the last item after the items already defined in the JSON data file.

Now run the following command from the terminal:


npm run test:consumer



We generate the contract again which now includes the post client test. Now if we re-run test:provider the provider will get the latest contract from the consumer that was created and assert if everything was generated correctly:


npm run test:provider



Now we can open the contract file again from the pacts folder and check if the post request is there.



As seen here the body and status are present. All expectations for contract interactions appear to be accounted for.

The next chapter will cover a more in-depth discussion of brokers and matchers.



Resources



© 2024 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR