In this tutorial, we will see how to set up a Consumer Pact Test, define the interaction, generate the contract for the API, and run the test with Mocha:
Consumer-Driven Contract Testing is driven by the consumer, thus we are starting with the Consumer Pact Test.
Pact.io supports multiple programming languages (incl. .Net, Ruby, Python, PHP), for the consumer test it makes sense to use a front-end language such as JavaScript.
A typical scenario when you will be implementing consumer-driven contract testing is where the consumer tests are written by the UI team and they are interacting with the backend API team as the provider.
=> Visit Here To See The Contract Testing Training Series For All
Table of Contents:
Write A Consumer Pact Test
The scenario for the tutorial will be:
Password strength, the consumer submits a JSON object containing the new password for the user and the API provider returns a 200 for valid passwords or 400 for invalid.
The consumer drives the test scenarios based on the API documentation and the way in which the consumer will interact with the API. In this tutorial, we will see how to set up a consumer test, define the interaction, and generate the contract for the API.
Referring to the scenario above, the user inputs the password by adhering to the suggested strength recommendations which must include at least: 1 uppercase character, 1 number, and 1 special character.
When an invalid password “p&ssw0rd1” is submitted, the API responds with a 400 HTTP code along with an error message: “Please enter a valid password, including at least 1 uppercase character”.
Installing Pact Dependencies
Installing pact-js relies on a few core packages in order to run the tests.
Mocha as the test runner, will provide the structure of the tests and reporting. Chai as the assertion library supports BDD style for the use of plain English. Axios to make API requests, which will be used to make the request to the mock server within the consumer test.
npm i -S @pact-foundation/pact@latest mocha chai axios
Initialize Pact and provide configuration options:
const { Pact } = require("@pact-foundation/pact"); describe("Pact", () => { const provider = new Pact({ consumer: "RegisterForm", provider: "PasswordService", port: 1234, log: path.resolve(process.cwd(), "logs", "pact.log"), dir: path.resolve(process.cwd(), "pacts"), logLevel: "INFO", }); });
consumer: Unique name of your micro-service
provider: Unique name of the provider API service
port: mock server port number to be used during the test
log: destination of pact logs
dir: destination of the contract file
Setup Pact Interaction
Pact interaction is like a test case. Providing the structure to set up the test data before the test starts, describe the test case, the request, and the expected response.
Input how you will interact with the API:
State:
- Data setup (E.g. user stored in the database)
uponReceiving:
- Interaction description
Request:
- URL (E.g. query params, route params)
- Content-Type (E.g. JSON, form-data)
- Body
Response:
- Expected body: Matching types (E.g. string, boolean, or array)
- Status Code (Example: 200)
- Content-Type (Example: JSON)
before(async () => await provider .setup() .then(() => provider.addInteraction({ state: "i have unique username", uponReceiving: "a request for valid password", withRequest: { method: "POST", path: "/password", headers: { Accept: "application/json" }, body: { password: "p&ssw0rd1" } }, willRespondWith: { status: 400, headers: { "Content-Type": "application/json" }, body: { error: "Bad Request", message: "Please enter a valid password, including at least 1 uppercase character" }, }, }) ) );
Once you have added all the interactions for your scenario. Interactions in contract testing should follow the behaviors based on API documentation.
Best Practices For Implementing Consumer Tests
#1) Use Match Types as much as possible. For example, an id with a hard-coded guid could be passed as Pact::Term defining the type and length of the id. This allows the state to manage to generate a unique guid during the provider tests.
#2) Don’t confuse contract tests with functional tests, you shouldn’t be including all the possible password options as pact interactions.
For example, one scenario checking password strength with 1 Uppercase character and a different scenario checking it with 1 special character. Both of these scenarios output the same API response thus it should be added as a part of unit tests.
#3) Consumer tests should be implemented at the API layer where the other dependencies are mocked or stubbed and is not included as part of the end-to-end tests.
#4) Ensure to follow best practices using Mocha setup and cleanup with beforeEach and afterEach. This will ensure that your mock server is only testing the endpoint/interaction you intended and will make debugging pact errors easier.
Core Areas To Focus While Thinking Of Consumer Scenarios
- Handling all response codes (e.g 200, 400, 500)
- HTTP methods (GET, POST, PUT, PATCH, DELETE)
- JSON data types
Consumer Mock Setup
This is where you make a request to the mock server. Basically what you are doing is creating a server with the API responses matching your interactions. Within the test, Pact spins up the mock server listening on the localhost.
As a part of the test, using Axios to make the HTTP request, a call is made to the localhost to double-check if the mock setup is identical to the actual call to be made in the code.
Pact states in the documentation and the tests should use the actual method to call the API that is being used in the code as you would in the unit tests. See how this ensures changes in the code update the contract tests, and in order to achieve this, you will need to isolate the code by making the call and ensure testability of the external dependencies.
This may not always be possible, thus considerations for contract tests should always be considered while updating API documentation or changing interactions with the API. In this example, we will be calling the API directly and catching the 400 error thrown.
it("invalid password on registration", async () => { await axios({ method: 'post', url: 'http://localhost:1235/password', headers: { Accept: "application/json" }, data: { password: 'p&ssw0rd1' } }) .then(() => {}).catch(error => { expect(error.response.data).to.have.deep.property("error", "Bad Request"); expect(error.response.data).to.have.deep.property ("message", "Please enter a valid password, including at least 1 uppercase character"); }); });
Create Contract File
As the output, a contract file is generated as a part of the final step in the test. Using the mocha afterEach & after functions in order to run the methods after each test and after all tests in the test suite.
Add the afterEach and after function below the it block, inside the describe block.
afterEach(() => provider.verify()); after(() => provider.finalize());
Run Consumer Test With Mocha
To execute the tests, you just need to add the mocha command to the script section of the package.json:
//package.json "scripts": { "test": "mocha" }
Open a new terminal and execute:
npm test
If you encounter any errors at this stage, then the reason will be the output to the console or within the “logs/pact.log”. If the request didn’t match the pact mock server then you will receive a message similar to “interaction not found”.
However, if all was successful, the JSON output will be located in “/pacts” and it should look something as shown below:
{ "consumer": { "name": "RegisterForm" }, "provider": { "name": "PasswordService" }, "interactions": [ { "description": "a request for valid password", "providerState": "i have unique username", "request": { "method": "POST", "path": "/password", "headers": { "Accept": "application/json" }, "body": { "password": "p&ssw0rd1" } }, "response": { "status": 400, "headers": { "Content-Type": "application/json" }, "body": { "error": "Bad Request", "message": "Please enter a valid password, including at least 1 uppercase character" } } } ], "metadata": { "pactSpecification": { "version": "2.0.0" } } }
Conclusion
Finally, you have the contract file, in the next tutorial, we will see how to publish the contract to the Pact Broker. The code for the consumer lives in their codebase, thus the Pact Broker allows the contract to be shared with the provider.
In this tutorial, we learned, which scenarios we would use to perform contract Pact tests and saw how to test different JSON data types. We have written our first consumer test using the pact-js framework and the next step is the hardest part, where you need to get buy-in from the other consumers.
=> Check ALL Contract Testing Tutorials Here