Implementing Consumer-Driven Contract tests in Angular


Consumer-driven means that the Consumer is responsible for specifying the interactions that it expects with the provider. Interactions are described in detail: HTTP verb, endpoint path, params, response status code, resource fields used, etc. and all of them together form the consumer contract.

Consumer contract tests exercise the Client code

We do not define “hand-craft” directly the contracts because they could get out of sync quickly and not reflect the real interaction that the consumer code performs. Instead what we do is unit test the layer of our consumer that interacts with the provider (the Client or Adapter code) using the Pact JS library to specify the interactions. If the unit tests pass a Pact contract file is generated with the contract between consumer and provider.

Implementing Consumer Contract tests with Pact

In an Angular front-end application, Consumer Contract tests are implemented using Jasmine and the Pact JS library.
They are similar to other unit tests for logic making HTTP requests. The difference is that instead of mocking the network responses using the Angular MockBackend

mockBackend.connections.subscribe(connection => {
connection.mockRespond(new Response(new ResponseOptions({body: RELEASE_TOGGLES})));

we allow the requests to go through the network against the pact-mock-service which verifies and replies according the expected interaction.

  describe('Delete Task', () => {

    beforeAll((done) => {
        state: 'a task with id task-id exists',
        uponReceiving: 'a request to delete task task-id',
        withRequest: {
          method: 'DELETE',
          path: '/tasks/task-id'
        willRespondWith: {
          status: 204
      }).then(done, e =>;

    it('should call the API to delete the task', (done) => {


Flexible Matching

It is vital that tests specify which are the resource fields the UI app cares about. But to not make tests rigid we’ll usually assert only the name and type of each field, not their exact content. To help with this, the Pact JS library provides flexible matching by regular expressions or type.

Next test is an example of a GET interaction where the expected resource and fields are defined using the type matcher Pact.Matchers.somethingLike.

  describe('Get all Tasks', () => {

    beforeAll((done) => {
        state: 'tasks exists',
        uponReceiving: 'a request to get tasks',
        withRequest: {
          method: 'GET',
          path: '/tasks'
        willRespondWith: {
          status: 200,
          headers: {'Content-Type': 'application/json'},
          body: [{
            id: Pact.Matchers.somethingLike('an id'),
            name: Pact.Matchers.somethingLike('a name'),
            done: Pact.Matchers.somethingLike(false),
            userId: Pact.Matchers.somethingLike('an user id')
      }).then(done, e =>;

    it('should return all tasks from API', (done) => {
      const tasks: Task[] = [{
        id: 'an id',
        name: 'a name',
        done: false,
        userId: 'an user id'

      tasksService.getAll().then(response => {

The mock server will return [{id: 'an id’, name: 'a name’, priority: 2, done: false}] in the consumer tests, but when the Pact verification is run in the provider, it will just check that the type of the name value is a String, and that the type of the priority value is a Number, etc.

To reiterate, the resource returned by the provider probably has other properties, but we should only describe the ones that the consumer uses.

Guidelines for state and uponReceiving descriptions

When we define the expected interactions between a consumer and a provider the ‘state’ and ‘uponReceiving’ descriptions used are important and should be written in a consistent style.

Later on, on the provider side, Pact uses the uponReceiving description to name the auto-generated test, and the state description is used to find a correspondent test state.

// Expected Interaction
  state: 'tasks exists',
  uponReceiving: 'a request to delete that tasks',
  withRequest: { method: 'DELETE', path: '/tasks/task-id' },
  willRespondWith: { status: 204 }

But the contracts are not only used by the tests, we also intend them to be user-friendly. Developers can consult contracts in the Pact Broker who formats them in a nice documentation style.

Sample scenarios

Note that the state affects what the producer should do and the uponReceiving affects the request that the consumer makes

Successful interaction where data is returned
state: ‘tasks for project with id project-id exist’,
uponReceiving: ‘a request to get tasks for that project’

Successful interaction where no data is returned
state: ‘tasks for project with id project-id do not exist’,
uponReceiving: ‘a request to get tasks for that project’

404 error interaction
state: ‘project project-id does not exist’,
uponReceiving: ‘a request to get tasks for that project’

Domain validation error interaction
state: ‘a task task-id exists and domain rules exist that prevent its modification’,
uponReceiving: ‘a request to update that task’

Successful interaction where data is created
state: ‘a project with name project-1 does not exist’,
uponReceiving: ‘a request to create a project with name project-1’

Pact spec files and tests setup

The contract tests files should be placed next to the code in the same way we do for the unit tests and use .pact.spec.js as file extension.

import { TestBed, getTestBed } from '@angular/core/testing';
import { HttpModule } from '@angular/http';
import * as Pact from 'pact-web';

describe('TasksService', () => {

  let provider;
  let tasksService;

  beforeAll((done) => {
    provider = Pact({ // 1)
      consumer: 'task-list-ui',
      provider: 'task-list-api',
      web: true

    // required for slower CI environments
    setTimeout(done, 200);

    // Required if run with `singleRun: false`

  afterAll((done) => {
    provider.finalize().then(done, e =>;

  beforeEach(() => {
    TestBed.configureTestingModule({  // 2)
      providers: [TasksService],
      imports: [HttpModule]

    tasksService = getTestBed().get(TasksService);

  afterEach((done) => {
    provider.verify().then(done, e =>;


  1. Instantiates a mock provider, indicating the name of the consumer app, the name of the provider server and the port of the pact-mock-service.
  2. Injecting the subject under test TasksService. In contract tests we use real HttpModule instead a stub one with MockBackend, it allows HTTP interactions to pass through to the pact mock server.

For any producer we interact, we should configure a karma proxy mapping. This is to redirect requests from our pact tests to the pact-mock-service running on a different port. This allows us to use partial URLs in our app code.

// karma.conf.js
proxies: {
  '/tasks': 'http://localhost:1234/tasks'

Running Consumer Contract tests

To run the tests use:
npm test

If the tests pass, the Pact-Mock-Service generates the pact contract files in the projects /pacts folder.

Those pacts, in a CDC testing spirit, can be used now to drive the development of the provider.

Implementing API client code

Client code should be minimal, acting as a facade that simplifies the usage of the provider API to the angular service layer hiding the HTTP details. If your angular service is simple, only dealing with the HTTP calls, you don’t need anything more. In the other hand if you have complex services I recommend to extract that responsibility to a specific Client or Adapter class.


In the next repository, you can find an example of consumer contract tests for the interactions between a task-list-ui app and a REST API backend.

To configure an Angular CLI project to support Pact tests check my previous article.



Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.