This workshop is aimed at demonstrating core features and benefits of contract testing with Pactflow and contract testing.
This workshop should take from 1 to 2 hours, depending on how deep you want to go into each topic.And it is fully self-driven so you can just follow each step to finish it at your pace.
Workshop outline
- Step 1 - Setup environment create consumer and provider apps.
- Step 2 - Add consumer tests - REST API Add the first consumer test for REST API.
- Step 3 - Add consumer tests - GraphQL Add the consumer test for GraphQL endpoint.
- Step 4 - Verify the provider Verify the pact at provider side.
- Step 5 - Back to the client we go Fix the consumer test.
- Step 6 - Handle error scenario Add error scenario.
- Step 7 - Add missing states Add missing states at provider.
NOTE: Each step is tied to, and must be run within, a git branch, allowing you to progress through each stage incrementally. For example, to move to a specific, you can run the following: git checkout [step_index]
NOTE: Please follow this guide to upgrade your local yarn to v3.
In this step, we need to first create an HTTP client to make the calls to our provider service:
The provider and consumer are built within a monorepo, managed by Lerna.
- Consumer: React + Apollo-client
- Provider: Nest.js App (enabling both REST and GraphQL)
To install the dependencies of the app, simply go to the root directory and run:
yarn install
We've set up two endpoints to use:
- GET /games - REST API to retrieve all the games.
- POST /graphql - Use GraphQL queries to fetch data.
There's no need to establish any database connection as we are simply using a mock json as the source. It is saved under /nest-provider/src/data/mock.json
feel free to update the data as you wish.
Running localhost:5000/graphql
will open the GraphQL playground in the browser so you can interactively debug and test the queries. You can use the following query to fetch the data in our example:
query Games {
games {
data {
id
name
url
reviews {
rating
comment
}
type
}
}
}
The api.js
has the REST api functions and we use apollo-client
with useQuery
hooks to fetch data via GraphQL endpoint.
Run the apps
To run the app, go to the root directory and run yarn start
. Alternatively, you can go to each package and run them independently.
Unit tests are written and executed in isolation of any other services. When we write tests for code that talk to other services, they are built on trust that the contracts are upheld. There is no way to validate that the consumer and provider can communicate correctly.
An integration contract test is a test at the boundary of an external service verifying that it meets the contract expected by a consuming service - Martin Fowler
Now is time to add the first Pact test. Because of the natature of consumer-driven, we are going to start with the consumer tests.
You can find the api class in /src/react-consumer/api.js
:
export class API {
constructor(url) {
if (url === undefined || url === '') {
url = process.env.REACT_APP_API_BASE_URL;
}
if (url.endsWith('/')) {
url = url.substr(0, url.length - 1);
}
this.url = url;
}
withPath(path) {
if (!path.startsWith('/')) {
path = '/' + path;
}
return `${this.url}${path}`;
}
async getGames() {
const res = await axios.get(this.withPath('/game')).then((r) => r.data);
return res;
}
}
Firstly, we need to install the dependencies required for Pact:
yarn add @pact-foundation/pact @pact-foundation/pact-node
Then create a /pact
folder under /src
, and add api.pact.spec.js
.
Add the Pact config to the top:
const provider = new Pact({
consumer: "YOUR_CONSUMER_NAME",
provider: "YOUR_PROVIDER_NAME",
log: path.resolve(process.cwd(), "logs", "pact.log"),
logLevel: "warn",
dir: path.resolve(process.cwd(), "pacts"),
spec: 2
});
NOTE: Please make sure you create the unique consumer and provider names, if using the same Pactflow account.
Then you can add the following test for GET /games
endpoint:
describe("API Pact test Game API - REST", () => {
beforeAll(() => provider.setup());
afterEach(() => provider.verify());
afterAll(() => provider.finalize());
describe("getting all games", () => {
test("games exists", async () => {
const expectedResult = {
id: 1,
name: "Heathstone",
type: "TCG",
url: "https://content.api.news/v3/images/bin/c316d90c344632190cbd595a42ac44ad",
reviews: [
{ rating: 5, comment: "Great game!" },
{ rating: 4, comment: "Good game!" },
]
};
// set up Pact interactions
await provider.addInteraction({
state: "games exist",
uponReceiving: "get all games",
withRequest: {
method: "GET",
path: "/game",
},
willRespondWith: {
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: eachLike(expectedResult),
},
});
const gameAPI = new API(provider.mockService.baseUrl);
// make request to Pact mock server
const games = await gameAPI.getGames();
expect(games).toStrictEqual([expectedResult]);
});
});
});
The above test looks very similar to the normal unit test with Jest.
This test starts a mock server a random port that acts as our provider service. To get this to work we update the URL in the Client that we create, after initialising Pact.
To simplify running the tests, add this to react-consumer/package.json
:
// add it under scripts
"test:pact": "CI=true react-scripts test --testTimeout 30000 pact.spec.js"
Running this test should pass, and it creates a pact file which we can use to validate our assumptions on the provider side, and have conversation around.
❯ yarn test:pact
PASS src/pact/api.pact.spec.js (14.28 s)
API Pact test Game API - REST
getting all games
✓ games exists (42 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.462 s
Ran all test suites matching /pact.spec.js/i.
You can run git checkout step2/add-consumer-rest-test
to complete this step too.
Okay, if everything goes well with the above steps, we are pretty much set up at the consumer side. However, accordingly to the API usage, most of the requests at Brighte are via GraphQL endpoints. Let's add the GraphQL Pact tests now.
To avoid testing the UI and component logic, instead of testing useQuery()
React hooks in Pact, we have to extract its business logic and test it independently.
Let's create a folder /graphql
to store all the queries and mutations, and add a file query.js
inside. Then, you can copy the query from App.js
, so the file looks like:
import { gql } from "@apollo/client";
const GET_GAMES_QUERY = gql`
query Games {
games {
data {
id
name
url
reviews {
rating
comment
}
type
}
}
}
`;
export {
GET_GAMES_QUERY,
};
And you can replace the code of App.js
with the above query:
import { GET_GAMES_QUERY } from './graphql/query';
...
const { loading, error, data } = useQuery(GET_GAMES_QUERY);
Once the above is done, we can add a new file /src/pact/graphql.pact.spec.js
and add the following config of Pact instance at the top:
const provider = new Pact({
consumer: "YOUR_CONSUMER_NAME",
provider: "YOUR_PROVIDER_NAME",
log: path.resolve(process.cwd(), "logs", "pact.log"),
logLevel: "warn",
dir: path.resolve(process.cwd(), "pacts"),
spec: 2,
pactfileWriteMode: 'merge',
});
The code should be very similar to step 2, but we've added pactfileWriteMode: 'merge'
here. Because if you run pact testing with multiple files, they can run in parallel and the final output file will be override by whichever test file runs last. With this option set to merge
, it will combine multiple pact test output into one pact file when it finishes testing.
Then you can add the following test for POST /graphql
endpoint:
describe("API Pact test Game API - GraphQL", () => {
let client;
beforeAll(async () => {
await provider.setup();
client = new ApolloClient({
link: new HttpLink({
uri: `${provider.mockService.baseUrl}/graphql`,
fetch,
}),
cache: new InMemoryCache({ addTypename: false }),
});
});
afterEach(async () => {
await provider.verify();
});
afterAll(async () => {
await provider.finalize();
client.stop();
});
test("games exists", async () => {
const expectedResult = {
id: 1,
name: "Heathstone",
type: "TCG",
url: "https://content.api.news/v3/images/bin/c316d90c344632190cbd595a42ac44ad",
reviews: [
{
rating: 5,
comment: "Great game!",
},
{
rating: 4,
comment: "Good game!",
},
]
};
const graphqlQuery = new GraphQLInteraction()
.uponReceiving("get games query")
.withQuery(print(GET_GAMES_QUERY))
.withOperation("Games")
.withVariables({})
.withRequest({
path: "/graphql",
method: "POST",
})
.willRespondWith({
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: {
data: {
games: {
data: eachLike(expectedResult),
},
},
},
});
provider.addInteraction(graphqlQuery);
const res = await client.query({
query: GET_GAMES_QUERY,
variables: {},
});
expect(res.data.games.data).toStrictEqual([expectedResult]);
});
});
A few things to be noticed here:
- We are calling
new GraphQLInteraction()
to add a GraphQL interaction to Pact test. It comes with some functions likewithQuery()
,withVariables()
, etc to set the GraphQL queries and inputs. - Initalise the apolloClient instance before the test:
client = new ApolloClient({
link: new HttpLink({
uri: `${provider.mockService.baseUrl}/graphql`,
fetch,
}),
cache: new InMemoryCache({ addTypename: false }),
});
- Set test query with
print()
in the GraphQL interaction
If everything goes well, you can run yarn test:pact
again and see the result:
❯ yarn test:pact
PASS src/pact/api.pact.spec.js (5.071 s)
PASS src/pact/graphql.pact.spec.js (5.25 s)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.998 s
Ran all test suites matching /pact.spec.js/i.
And if you go to /pacts
folder you should see the generated pact file with both REST and GraphQL tests inside.
We need to make the pact file (the contract) that was produced from the consumer test available to the Provider module. This will help us verify that the provider can meet the requirements as set out in the contract. For now, we'll hard code the path to where it is saved in the consumer test, in the later steps we investigate a better way of doing this.
Install the dependency first:
> yarn add @pact-foundation/pact
Now let's make a start on writing Pact tests to validate the consumer contract:
Create a file in /nest-provider/src/game/game.pact.spec.ts
and add the config at the top:
const opts: typeof VerifierOptions = {
logLevel: 'info',
providerBaseUrl: 'http://localhost:4000',
provider: 'YOUR_PROVIDER',
providerVersion: '1.0.0',
pactUrls: [
path.resolve(__dirname, '../../../react-consumer/pacts/YOUR_PRACT_FILE.json')
]
};
Then add the following test to verify the pact file:
describe('Pact Verification', () => {
it('validates the expectations of ProductService', () => {
if (process.env.CI || process.env.PACT_PUBLISH_RESULTS) {
Object.assign(opts, {
publishVerificationResult: true,
});
}
return new Verifier(opts).verifyProvider().then(output => {
console.log(output);
}).finally(() => {
app.close();
});
})
});
To simplify running the tests, add this to nest-provider/package.json:
// add it under scripts
"test:pact": "CI=true npx jest --testTimeout=30000 --testMatch \"**/*.pact.spec.ts\""
We now need to validate the pact generated by the consumer is valid, by executing it against the running service provider, which should fail:
Failures:
1) Verifying a pact between YOUR_CONSUMER and YOUR_PROVIDER Given games exist get all games with GET /game returns a response which has status code 200
Failure/Error: expect(response_status).to eql expected_response_status
expected: 200
got: 404
(compared using eql?)
2) Verifying a pact between YOUR_CONSUMER and YOUR_PROVIDER Given games exist get all games with GET /game returns a response which has a matching body
Failure/Error: expect(response_body).to match_term expected_response_body, diff_options, example
Actual: {"statusCode":404,"message":"Cannot GET /game","error":"Not Found"}
Diff
--------------------------------------
Key: - is expected
+ is actual
Matching keys and values are not shown
-[
- {
- "id": 1,
- "name": "Heathstone",
- "type": "TCG",
- "url": "https://content.api.news/v3/images/bin/c316d90c344632190cbd595a42ac44ad",
- "reviews": [
- {
- "rating": 5,
- "comment": "Great game!"
- },
- {
- "rating": 4,
- "comment": "Good game!"
- },
- ]
- },
-]
+{
+ "statusCode": 404,
+ "message": "Cannot GET /game",
+ "error": "Not Found"
+}
Description of differences
--------------------------------------
* Expected an Array (like [{"id"=>1, "name"=>"Heathstone", "type"=>"TCG", "url"=>"https://content.api.news/v3/images/bin/c316d90c344632190cbd595a42ac44ad", "reviews"=>[{"rating"=>5, "comment"=>"Great game!"}, {"rating"=>4, "comment"=>"Good game!"}]}]) but got a Hash at $
2 interactions, 1 failure
Failed interactions:
* Get all games given games exist (to re-run just this interaction, set environment variables PACT_DESCRIPTION="get all games" PACT_PROVIDER_STATE="games exist")
WARN: Cannot publish verification for YOUR_CONSUMER as there is no link named pb:publish-verification-results in the pact JSON. If you are using a pact broker, please upgrade to version 2.0.0 or later.
at ChildProcess.<anonymous> (../node_modules/@pact-foundation/pact-node/src/verifier.ts:275:58)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 4.939 s, estimated 5 s
The test has failed, as the expected path GET /game is returning 404.
The correct endpoint which the consumer should call is GET /games.
We now need to update the consumer client and tests to hit the correct api path.
First, we need to update the GET route for the client:
In react-consumer/src/api.js
:
async getGames() {
const res = await axios.get(this.withPath('/games')).then((r) => r.data);
return res;
}
Then we need to update the Pact test to use the correct endpoint in path.
In react-consumer/src/pact/api.pact.spec.js
:
await provider.addInteraction({
state: "games exist",
uponReceiving: "get all games",
withRequest: {
method: "GET",
path: "/games",
},
willRespondWith: {
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: eachLike(expectedResult),
},
});
Let's run and generate an updated pact file on the client:
> yarn test:pact
PASS src/pact/graphql.pact.spec.js
PASS src/pact/api.pact.spec.js
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.417 s
Ran all test suites matching /pact.spec.js/i.
Now we run the provider tests again with the updated contract, run the command:
PASS src/game/game.pact.spec.ts (8.391 s)
Pact Verification
✓ validates the expectations of ProductService (1389 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.535 s
Ran all test suites.
Jest did not exit one second after the test run has completed.
Yay - green ✅!
What happens when we make a call for the endpoint that doesn't exist? We assume we'll get a 404.
Let's write a test for these scenarios, and then generate an updated pact file.
In nest-provider/src/game/game.service.ts
we add a new function to fetch a single game:
async getGame (id: number): Promise<Game> {
const game = gamesData.data.find(game => game.id == id);
return game;
}
And add a new route to nest-provider/src/game/game.controller.ts
:
@Get('game/:id')
async getGame(@Param('id') id: number) {
const response = await this.gameService.getGame(id);
if (!response) {
throw new NotFoundException('Invalid game')
}
return response;
}
In react-consumer/src/api.js
add the following method:
async getGame(id) {
const res = await axios.get(this.withPath(`/game/${id}`)).then((r) => r.data);
return res;
}
In react-consumer/src/pact/api.pact.spec.js
we add a new interaction to handle the error:
describe('get one game', () => {
test("game does not exist", async () => {
// set up Pact interactions
await provider.addInteraction({
state: 'game with id 3 does not exist',
uponReceiving: 'get game with id 3',
withRequest: {
method: 'GET',
path: '/game/3'
},
willRespondWith: {
status: 404
},
});
const gameAPI = new API(provider.mockService.baseUrl);
// make request to Pact mock server
await expect(gameAPI.getGame(3)).rejects.toThrow('Request failed with status code 404');
});
})
After you've done every change above, you can run pact test in the terminal now to generate a new pact file (you can remove the old one manually):
> yarn test:pact
PASS src/pact/api.pact.spec.js
PASS src/pact/graphql.pact.spec.js
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.964 s
Ran all test suites matching /pact.spec.js/i.
Now if you go back to provider side and run yarn test:pact
, you should see the following output:
FAIL src/game/game.pact.spec.ts (7.641 s)
Pact Verification
✕ validates the expectations of ProductService (1317 ms)
● Pact Verification › validates the expectations of ProductService
INFO: Reading pact at /Users/mattyao/Documents/Work/Brighte/pactflow-workshop/packages/react-consumer/pacts/your_consumer-your_provider.json
Verifying a pact between YOUR_CONSUMER and YOUR_PROVIDER
Given games exist
get all games
with GET /games
returns a response which
has status code 200
has a matching body
includes headers
"Content-Type" which equals "application/json; charset=utf-8"
Given game with id 99 does not exist
get game with id 99
with GET /game/3
returns a response which
has status code 404 (FAILED - 1)
Get games query
with POST /graphql
returns a response which
has status code 200
has a matching body
includes headers
"Content-Type" which equals "application/json; charset=utf-8"
Failures:
1) Verifying a pact between YOUR_CONSUMER and YOUR_PROVIDER Given game with id 99 does not exist get game with id 99 with GET /game/3 returns a response which has status code 404
Failure/Error: expect(response_status).to eql expected_response_status
expected: 404
got: 200
(compared using eql?)
3 interactions, 1 failure
Failed interactions:
* Get game with id 99 given game with id 99 does not exist (to re-run just this interaction, set environment variables PACT_DESCRIPTION="get game with id 99" PACT_PROVIDER_STATE="game with id 99 does not exist")
WARN: Cannot publish verification for YOUR_CONSUMER as there is no link named pb:publish-verification-results in the pact JSON. If you are using a pact broker, please upgrade to version 2.0.0 or later.
at ChildProcess.<anonymous> (../node_modules/@pact-foundation/pact-node/src/verifier.ts:275:58)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 7.758 s, estimated 8 s
Ran all test suites.
Jest did not exit one second after the test run has completed.
We expected this failure, because the game we are requesing (id=3) does in fact exist! What we want to test for, is what happens if there is a different state on the Provider. This is what is referred to as "Provider states", and how Pact gets around test ordering and related issues.
We could resolve this by updating our consumer test to use a known non-existent game, but it's worth understanding how Provider states work more generally.
Our code already deals with missing users and sends a 404 response, however our test data fixture always has game id=3 in our database.
In this step, we will add a state handler
(stateHandlers) to our provider Pact verifications, which will update the state of our data store depending on which states the consumers require.
States are invoked prior to the actual test function is invoked. You can see the full lifecycle here.
Because Nest.js has its own way of Dependency Injection (DI), the set-up following the Pact documentation to use state handlers
to manage the provider state wouldn't work.
We are going to reafactor our provider code with the help of the library nestjs-pact, which solves a lot of tricky bits using Pact with Nest.js. There are two main modules suggested; one for the Provider role (Verifier), and one for the Consumer role (creating Pact files and publish), each loaded separately. In this step, we will only do the minimum changes to use the provider module.
Firstly, we need to install nestjs-pact
library:
> yarn add nestjs-pact
We need to create some additional files, so let's create a folder under nest-provider/src/game/pact
and then create a file pact.service.ts
with in there, with the following content:
import { Injectable } from '@nestjs/common';
import { PactProviderOptionsFactory, PactProviderOptions } from 'nestjs-pact';
import { GameService } from '../game.service';
const path = require('path');
@Injectable()
export class PactProviderConfigOptionsService
implements PactProviderOptionsFactory
{
public constructor(private readonly gameService: GameService) {}
public createPactProviderOptions(): PactProviderOptions {
return {
logLevel: 'debug',
enablePending: true,
provider: 'YOUR_PROVIDER',
providerVersion: '1.0.0',
pactUrls: [
path.resolve(__dirname, '../../../../react-consumer/pacts/your_consumer-your_provider.json')
],
stateHandlers: {
'game with id 3 does not exist': async () => {
this.gameService.clear();
return 'invalid game id';
},
'games exist': async () => {
this.gameService.importData();
return 'all games are returned - REST';
},
'query for games': async () => {
this.gameService.importData();
return 'all games are returned - GraphQL';
},
},
};
}
}
Notice here in the stateHandlers
fields, we've defined three different provider states to align with the generated contact:
- games exist (REST)
- game with id 3 does not exist (REST)
- query for games (GraphQL)
Then, we need to create a pact.module.ts
to inject this service:
import { Module } from '@nestjs/common';
import { PactProviderModule } from 'nestjs-pact';
import { PactProviderConfigOptionsService } from './pact.service';
import { GameService } from '../game.service';
import { GameModule } from '../game.module';
@Module({
imports: [
PactProviderModule.registerAsync({
imports: [GameModule],
useClass: PactProviderConfigOptionsService,
inject: [GameService],
}),
],
})
export class PactModule {}
Lastly, we have to update the provider verification game.pact.spec.ts
:
import { Test } from '@nestjs/testing';
import { PactVerifierService } from 'nestjs-pact';
import { INestApplication, Logger, LoggerService } from '@nestjs/common';
import { GameModule } from '../game.module';
import { GameService } from '../game.service';
import { PactModule } from './pact.module';
import { GraphQLModule } from '@nestjs/graphql';
jest.setTimeout(30000);
describe('Pact Verification', () => {
let verifier: PactVerifierService;
let logger: LoggerService;
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
GameModule,
PactModule,
GraphQLModule.forRoot({
autoSchemaFile: true,
playground: true,
path: '/graphql',
}),
],
providers: [GameService, Logger],
}).compile();
verifier = moduleRef.get(PactVerifierService);
logger = moduleRef.get(Logger);
app = moduleRef.createNestApplication();
await app.init();
});
it("Validates the expectations of 'Matching Service'", async () => {
const output = await verifier.verify(app);
logger.log('Pact Verification Complete!');
logger.log(output);
});
afterAll(async () => {
await app.close();
});
});
And now, if you run provider test again, everything should work now:
> yarn test:pact
7.1: Pact Verification succeeded.
PASS src/game/pact/game.pact.spec.ts (7.81 s)
Pact Verification
✓ Validates the expectations of 'Matching Service' (1188 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 7.96 s, estimated 8 s
Ran all test suites.
We've been publishing our pacts from the consumer project by essentially sharing the file system with the provider. But this is not very manageable when you have multiple teams contributing to the code base, and pushing to CI. We can use a Pactflow to do this instead.
If you are not familiar with Pactflow, please check their awesome docs on the website. In general, it is a SaaS platform that solves the complex challenges of testing micro-services and API integrations allowing companies with multiple development teams to orchestrate contract testing at scale. Using a hosted pact broker with Pactflow, will allow you to concentrate on testing your application without having to worry about managing infrastructure, along with a number of other useful features.
Alternatively, you can achieve to publish the pact contract with the OSS Pact Broker. But in this workshop example, we will learn how to use Pactflow to manage this.
Create a new Pactflow account and signup to the free Starter Plan. You will be emailed a set of credentials to access your account, these credentials are only for accessing the UI.
Grab your API Token(Click on settings -> API Tokens -> Read/write token -> COPY ENV VARS) and set the environment variables in your terminal as follows:
export PACT_BROKER_BASE_URL=https://<your_broker_name>.pactflow.io
export PACT_BROKER_TOKEN=exampleToken
First, let's update the authentication in react-consumer
and add publish.pact.js
.
import { execSync } from 'child_process';
import { resolve } from 'path';
import pact from '@pact-foundation/pact-node';
const { publishPacts } = pact;
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
const gitHash = execSync('git rev-parse --short HEAD').toString().trim();
const pactBrokerUrl = 'YOUR_PACFLOW_URL'; // your pactflow url, usually it is {YOUR_DOMAIN}.pact.io
const opts = {
pactFilesOrDirs: [resolve(process.cwd(), 'pacts')],
pactBroker: pactBrokerUrl,
pactBrokerToken: 'PACTFLOW_TOKEN', // copy the API token and put it here
tags: ['prod', 'test'],
consumerVersion: gitHash,
branch,
};
publishPacts(opts).catch((e) => {
console.error(`Pact contract publishing failed at ${pactBrokerUrl}: `, e);
});
Then, you will need to add the command to run publish in react-consumer/package.json -> scripts
:
"publish:pact": "node publish.pact.js"
Note in this example we simply copy and paste the API token to the script, which works as expected. However, in the real world it is not safe to keep the API token in your source code. The best practice is to store them in the CI environment as the encrypted variables and load them into the code.
Now you can run yarn publish:pact
in the terminal, if everything above is set up correctly, you should see the following output:
yarn publish:pact
pact-node@10.17.1: Publishing Pacts to Broker
Publishing pacts to broker at: https://{YOUR_DOMAIN}.pactflow.io
Created YOUR_CONSUMER version abcde123 with tags prod, test
Next steps:
Configure the version branch to be the value of your repository branch.
Pact successfully published for YOUR_CONSUMER version abcde123 and provider YOUR_PROVIDER.
View the published pact at https://{YOUR_DOMAIN}.pactflow.io/pacts/provider/YOUR_PROVIDER/consumer/YOUR_CONSUMER/version/abcde123
Events detected: contract_published, contract_content_changed (first time any pact published for this consumer with consumer version tagged prod, first time any pact published for this consumer with consumer version tagged test)
Next steps:
* Add Pact verification tests to the YOUR_PROVIDER build. See https://docs.pact.io/go/provider_verification
* Configure separate YOUR_PROVIDER pact verification build and webhook to trigger it when the pact content changes. See https://docs.pact.io/go/webhooks
And now login to your Pactflow account, you will see the pact contract has been uploaded successfully but is "Unverified":
You've completed the consumer side tasks and it is time to look at the provider now. Let's go to package/nest-provider/src/game/pact/pact.service.ts
and update the pact options as the following:
public createPactProviderOptions(): PactProviderOptions {
return {
... // copy from the previous step
pactBrokerUrl: 'https://{YOUR_DOMAIN}.pactflow.io',
pactBrokerToken: 'YOUR_API_TOKEN',
publishVerificationResult: process.env.CI === 'true',
...
};
}
It will publish the verification result to Pactflow when:
- The tests are all passed
- The command of
yarn test:pact
is running under the CI environment withCI=true
Let's run the provider verification one last time after this change. It should now verifying your contract and update the result in Pactflow.
That's it - you're now not a Pact newbie anymore. Go to the doc site and find out more advanced usage of Pactflow!