20 March 2018 - on 

After having seen a high level overview of how we can use Consumer Driven Contracts to extend the use of BDD in a microservice architecture beyond the edge services, in this article I'm going in more detail in how to implement it starting from the frontend application.

If you have not yet read the first article of the series, it would be a good moment to do it, so you can understand the motivation and which is the whole idea behind it. You can read it in BDD in a Microservices Environment using Consumer Driven Contract Testing (1).

You can find the code of the sample application in https://github.com/jagilpe/bdd-pact-microservices.

The web frontend application

The web frontend of the application is a SPA based on AngularJS 5, although there should not have been any problem to choose another technology for it. As BDD framework cucumber-js is used, and for the consumer driven contracts Pact.

The frontend application is in the ng-frontend directory of the sample code repository.

Setting up the BDD framework

The first step is to create a new angular project and configure it to use cucumber for the end to end testing instead of jasmine. For this you can simply follow the instructions in BDD with AngularJS. Setting up the project to use Cucumber.js with Protractor.

Set up the Consumer Driven Contract framework

In a previous article we talked about how to use MockServer to mock the backend in the E2E tests of our angular application. In this case I've used a similar approach, but instead of MockServer, the consumer side of Pact-JS is used to mock the API backend. With Pact the mock provider used in the E2E testing besides answering with the responses the application is expecting, it records all the interactions and in the end it generates a contract file that the real provider will have to fulfill.

First we have to add pact to the development dependencies of our project.

yarn add pact --dev

In the sample project the cucumber hooks are used to manage the lifecycle of the mock provider.

// ng-frontend/e2e/pact/init-pact.step.ts

import { defineSupportCode } from 'cucumber';
import * as Provider from 'pact';
import * as path from 'path';
import { API_BACKEND_PORT } from '../../src/environments/environment.e2e';

export let apiBackend: Provider.PactProvider;

defineSupportCode(({ AfterAll, BeforeAll, After }) => {

  // The configuration of the mock provider
  apiBackend = Provider({
    consumer: 'ng-frontend',
    provider: 'api-gateway',
    spec: 2,
    port: API_BACKEND_PORT,
    cors: true,
    dir: path.resolve(__dirname, '../../build/pacts')
  });

  // We start the mock provider before the tests
  BeforeAll((callback) => apiBackend.setup());

  // After finalizing the tests we shut down the mock provider
  AfterAll(() => apiBackend.finalize());

  // After each test we have to call verify to check the expected interactions
  After(() => apiBackend.verify());
});

Step definitions

Now that we have the project prepared to use cucumber and pact for our E2E tests, we can start implementing our scenarios. For the sake of clarity, we are going to focus on only one of the scenarios we want to implement: 'Get a list of products of a category'. We define the scenario as we usually do with cucumber.

# ng-frontend/e2e/features/catalogue-browse.feature

Feature: Browse the product catalogue
  As a user I want to browse the product catalogue

  Scenario: Get a list of products of a category
    Given there are 5 products in the "Smartphones" category
    When I go to the "Smartphones" category page
    Then I should get 5 items in the products list

We now have to implement the steps used in the scenario. The interactions with the backend API regarding the product catalogue are defined in the product-catalogue.steps.ts file. In particular for this scenario:

// ng-frontend/e2e/step-definitions/product-catalogue.steps.ts

import { defineSupportCode } from 'cucumber';
import { apiBackend } from '../pact/init-pact.step';

const pact = require('pact');
const { eachLike } = pact.Matchers;

defineSupportCode(({ Given }) => {

  const categoryIds = {
    "Smartphones": { id: 1, name: "Smartphones" }
  };

  const product = {
    id: 1,
    name: 'iPhone 8',
    manufacturer: 'Apple'
  };

  Given('there are {int} products in the {string} category', (itemCount: number, category: string) => {
    const categoryId = categoryIds[category].id;
    return apiBackend.addInteraction({
      state: `there are ${itemCount} products in the category ${categoryId}`,
      uponReceiving: `A request for products in the category ${categoryId}`,
      withRequest: {
        method: 'GET',
        path: '/api/v1/products',
        query: { 'category': `${categoryId}`}
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: eachLike(product, { min: itemCount })
      }
    });
  });
  // ...
});

What we have to make basically is define the interactions we are expecting with our backend using the addInteraction method. This method returns a Promise that we can directly return in our step definition. For more details about how to define this interactions you can read the Pact documentation.

The only thing left now is to define the other steps of our scenario.

// ng-frontend/e2e/step-definitions/catalogue-page.steps.ts

import { defineSupportCode } from 'cucumber';
import { CategoryPo } from '../pages/category.po';

const chai = require('chai').use(require('chai-as-promised'));
const expect = chai.expect;

defineSupportCode(({ When, Then }) => {
  let categoryPage: CategoryPo;

  const categoryIds = {
    "Smartphones": 1
  };

  When('I go to the {string} category page', (category: string) => {
    categoryPage = new CategoryPo(categoryIds[category]);
    return categoryPage.navigateTo();
  });

  Then('I should get {int} items in the products list', (count: number) => {
    return expect(categoryPage.getProductsCount()).to.eventually.equal(count);
  });
});

In this steps the PageObject pattern is used to keep the code clearer by encapsulating the access to the elements of the page.

// ng-frontend/e2e/pages/category.po.ts

import { browser, by, element, promise } from 'protractor';

export class CategoryPo {

  constructor(private categoryId: number) {}

  navigateTo(): promise.Promise<void> {
    return browser.get(`/category/${this.categoryId}`);
  }

  getProductsCount(): promise.Promise<number> {
    return element.all(by.css('#product-list .product')).count();
  }
}

Contract generation

Once the scenario is implemented and the E2E tests are green, pact will generate a contract file from the interactions we defined in our Given steps. 

The path in which this file is created is configured when creating the mock provider. In our case is defined in the file init-pact.step.ts, and points out to ng-frontend/build/pacts

If we build the frontend application running the :ng-frontend:build task a pact file will generated. From the project root directory run:

./gradlew :ng-frontend:build

You can also generate the pact file simply running the E2E tests of the angular project. From the ng-frontend directory run:

yarn e2e

The pact file will contain all the expectations that the frontend application has from the API backend. In our case the file is called ng-frontend-api-gateway.json and it contains the expected interactions between the ng-frontend service (the consumer side) and the api-gateway service (provider side).

Share the consumer contract with the providers

Now that the pact file has been generated we have to share it with the team that will build the provider service. We can directly share this file with them so that they can use it as source with their provider side of Pact or we can use a Pact Broker.

In the sample application a pact broker is started using docker compose (docker-compose/pact-broker-compose.yml), and then used to share the pacts between the different consumers and providers.

To start the broker you can simply use the pactBrokerComposeUp gradle task or directly docker-compose. From the project's root directory run:

./gradlew pactBrokerComposeUp

# or
docker-compose -f docker-compose/pact-broker-compose.yml up

To publish the pact generated by the frontend application to the pact broker we have once again two possibilities. We can publish them directly from our E2E tests using the Pact-JS API from a cucumber AfterAll hook, or we can make it from our build tool.

In the sample code the pact gradle plugin is used to publish the pact to the pact broker.

// ng-frontend/build.gradle

// ...
// Load the plugin
plugins {
    id "au.com.dius.pact" version "3.2.13"
    // ...
}

// ...
// Configure the pacts location and the broker url
pact {
    publish {
        pactDirectory = "$projectDir/build/pacts"
        pactBrokerUrl = 'http://localhost:1080'
    }
}

// ...
// This task will generate the pact file
task endToEndTests(type: NodeTask) {
    dependsOn yarn
    script = file('node_modules/.bin/ng')
    args = ["e2e", "--environment=e2e"]
}

// ...
// Run the E2E test before publishing the pacts
pactPublish.dependsOn endToEndTests
// We have to be sure that the broker is running before trying to publish the pacts
pactPublish.dependsOn ':pactBrokerComposeUp'

// ...

If we run the :ng-frontend:pactPublish gradle task it will start the broker, run the E2E tests of the frontend application and at the end it will publish the generated pact to the broker.

./gradlew :ng-frontend:pactPublish

Now we will be able to see the generated pact accessing the pact broker in http://localhost:1080.

Pact broker after publishing the frontend consumer contract

What's next?

After generating the consumer contract between the front end application and the API Gateway, now it's time to explore how to continue with the implementation of the API Gateway. You can find the details in the next article of the series.

Rest API Gateway using Netflix Zuul and Pact Consumer Driven Contracts