Category Archives: Software Development

Using step definition code from Given and When contexts

Have you ever wanted to use a single step definition like I create file '(.*)' from Given and When contexts in a feature file like this?

Scenario: CreateFile
    When I create file 'hello.txt'
    Then ...

Scenario DeleteFile
    Given I create  file 'hello.txt'
    When I delete 'hello.txt'

Here’s how to have 1 method that implements both the Given and the When I create file '...'

Create a class like this:

public class GivenWhenAttribute : StepDefinitionBaseAttribute
{
readonly static StepDefinitionType[] types = new[] { StepDefinitionType.Given, StepDefinitionType.When };
public GivenWhen() : this(null)  {  }
public GivenWhen(string regex) : base(regex, types ) { }
public GivenWhen(string regex, string culture) : this(regex) { Culture = culture; }
}

Use it in in the [Binding] classes like this:

[GivenWhen("I create file '(.*)'")]
public void CreateFile(String Name) { File.Create(Name); }

Adding PACT Contract Testing to an existing golang code base

Here’s how to add a Contract Test to a Go microservice, a provider in pact terminology, using pact-go This post uses v1 of pact-go, as the V2 version is stil in beta.

Install PACT cli tools on your dev machine

A linux or dev-container based environment can just use the same approach as the CI pipeline documented later on in this post.

For a Windows dev machine, install the pact-cli tools like this:

  1. Use browser to download https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v2.0.2/pact-2.0.2-windows-x86.zip
  2. unzip to c:\Repos
  3. Change PATH to include c:\repos\pact\bin
  4. Restart any editors or terminals
  5. Run go install gopkg.in/pact-foundation/pact-go.v1

Create a unit test to validate the service meets the contract/pact

Add unit test like below. Notice these settings and how the environment variables affect them. This helps when tests need to run both on the local development machine as well as on CI/CD machines.

Setting Effect
PublishVerificationResults Controls if pact should publish the verification result to the broker. For my local dev machine I dont publish. From the CI pipeline I do publish
ProviderBranch The name of the branch for this provider version.
ProviderVersion The version of this provider. My CI pipeline ensures variable PACT_PROVIDER_VERSION contains the unique build number. On my local machine its just set to 0.0.0
package app
import (
    "fmt"
    "bff/itemrepository"
    "os"
    "strconv"
    "strings"
    "testing"

    "github.com/pact-foundation/pact-go/dsl"
    "github.com/pact-foundation/pact-go/types"
    "github.com/stretchr/testify/assert"
)

func randomItem(env string, name string) itemrepository.item{
    return itemrepository.item{
        Environment:               env,
        Name:                      name,
    }
}

func getEnv(name string, defaultVal string) string {
    tmp := os.Getenv(name)
    if len(tmp) > 0 {
        return tmp
    } else {
        return defaultVal
    }
}

func getProviderPublishResults() bool {
    tmp, err := strconv.ParseBool(getEnv("PACT_PROVIDER_PUBLISH", "false"))
    if err != nil {
        panic(err)
    }
    return tmp
}

func TestProvider(t *testing.T) {
    //Arrange: Start the service in the background.
    port, repo, _ := startApp(false)
    pact := &dsl.Pact{ Consumer: "MyConsumer", Provider: "MyProvider", }

    //Act: Let pact spin-up a mock client to verifie our service.
    _, err := pact.VerifyProvider(t, types.VerifyRequest{
            ProviderBaseURL:            fmt.Sprintf("http://localhost:%d", port),
            BrokerURL:                  getEnv("PACT_BROKER_BASE_URL", ""),
            BrokerToken:                getEnv("PACT_BROKER_TOKEN", ""),
            PublishVerificationResults: getProviderPublishResults(),
            ProviderBranch:             getEnv("PACT_PROVIDER_BRANCH", ""),
            ProviderVersion:            getEnv("PACT_PROVIDER_VERSION", "0.0.0"),
            StateHandlers: types.StateHandlers{
                "I have a list of items": func() error {
                    repo.Set("env1", []itemrepository.item{randomItem("env1", "tenant1")})
                        return nil
                },
            },
        })
    pact.Teardown()

    //Assert
    assert.NoError(t, err)
}

Ensure the CI pipeline runs the test and publishes verification results

For my CI builds, I run this to make sure the CI machine has the pact-cli tools installed
cd /opt
curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-ruby-standalone/master/install.sh | bash
export PATH=$PATH:/opt/pact/bin
go install github.com/pact-foundation/pact-go@v1
...
pipeline already runs the unit tests
...

Check `can-i-deploy` in the CD pipeline

Change the CD pipeline to

  • verify this version of the service has passed the contract test, Otherwise do not deploy to production
  • After successful deployment, inform broker which new version is running in production.

My CD pipeline runs on different machines, so it again has to ensure PACT is installed, just like the CI pipeline:

# pipeline variables
PACT_PACTICIPANT=MyProvider
PACT_ENVIRONMENT=production
PACT_BROKER=https://yourtenant.pactflow.io
PACT_BROKER_TOKEN=...a read/write token...preferably a system user to represent CI/CD actions...

# task to install PACT
cd /opt
curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-ruby-standalone/master/install.sh | bash
echo "##vso[task.prependpath]/opt/pact/bin"
...
...
# Task to check if the version of the build can be deployed to production
pact-broker can-i-deploy --pacticipant $PACT_PACTICIPANT --version $BUILD_BUILDNUMBER --to-environment $PACT_ENVIRONMENT --broker-base-url $PACT_BROKER --broker-token $PACT_BROKER_TOKEN
...
#Do whatever tasks you need to deploy the build to the environment
...
...
#Task to record the deployment of this version of the producer to the environment
pact-broker record-deployment --environment $PACT_ENVIRONMENT --version $BUILD_BUILDNUMBER --pacticipant $PACT_PACTICIPANT --broker-base-url $PACT_BROKER --broker-token $PACT_BROKER_TOKEN

Adding PACT Contract Testing to an existing TypeScript code base

I like Contract Testing! I added a contract test with PACT-js and Jest for my consumer like this:

Installing PACT

  1. Disable the ignore-scripts setting: npm config set ignore-scripts false
  2. Ensure build chain is installed. Most linux based CI/CD agents have this out of the box. My local dev machine runs Windows; according to the installation guide for gyp the process is:
    1. Install Python from the MS App store. This takes about 5 minutes.
    2. Ensure the machine can build native code. My machine had Visual Studio already so I just added the ‘Desktop development with C++’ workload using the installer from ‘Tools -> Get Tools and Features’ This takes about 15 – 30 minutes
    3. npm install -g node-gyp
  3. Install the PACT js npmn package: npm i -S @pact-foundation/pact@latest
  4. Write a unit test using either the V3 or V2 of the PACT specification. See below for some examples.
  5. Update your CI build pipeline to publish the PACT like this: npx pact-broker publish ./pacts --consumer-app-version=$BUILD_BUILDNUMBER --auto-detect-version-properties --broker-base-url=$PACT_BROKER_BASE_URL --broker-token=$PACT_BROKER_TOKEN

A V3 version of a PACT unit test in Jest

//BffClient is the class implementing the logic to interact with the micro-service.
//the objective of this test is to:
//1. Define the PACT with the microservice
//2. Verify the class communicates according to the pact

import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import path from 'path';
import { BffClient } from './BffClient';

// Create a 'pact' between the two applications in the integration we are testing
const provider = new PactV3({
    dir: path.resolve(process.cwd(), 'pacts'),
    consumer: 'MyConsumer',
    provider: 'MyProvider',
});

describe('GET /', () => {
    it('returns OK and an array of items', () => {
        const exampleData: any = { name: "my-name", environment: "my-environment", };

        // Arrange: Setup our expected interactions. Pact mocks the microservice for us.
        provider
            .given('I have a list of items')
            .uponReceiving('a request for all items')
            .withRequest({method: 'GET', path: '/', })
            .willRespondWith({
                status: 200,
                headers: { 'Content-Type': 'application/json' },
                body: MatchersV3.eachLike(exampleData),
            });
        return provider.executeTest(async (mockserver) => {
            // Act: trigger our BffClient client code to do its behavior 
            // we configured it to use the mock instead of needing some external thing to run
            const sut = new BffClient(mockserver.url, "");
            const response = await sut.get()

            // Assert: check the result
            expect(response.status).toEqual(200)
            const data:any[] = await response.json()
            expect(data).toEqual([exampleData]);
        });
    });
});

A V2 version

import { Pact, Matchers } from '@pact-foundation/pact';
import path from 'path';
import { BffClient } from './BffClient';

// Create a 'pact' between the two applications in the integration we are testing
const provider = new Pact({
    dir: path.resolve(process.cwd(), 'pacts'),
    consumer: 'MyConsumer',
    provider: 'MyProvider',
});

describe('GET', () => {
    afterEach(() => provider.verify());
    afterAll(() => provider.finalize());

    it('returns OK and array of items', async () => {
        const exampleData: any = { name: "my-name", environment: "my-environment", };
        // Arrange: Setup our expected interactions. Pact mocks the microservice for us.
        await provider.setup()
        await provider.addInteraction({
            state: 'I have a list of items',
            uponReceiving: 'a request for all items',
            withRequest: { method: 'GET', path: '/',  },
            willRespondWith: {
                status: 200,
                headers: { 'Content-Type': 'application/json' },
                body: Matchers.eachLike(exampleData),
            },
        })

        // Act: trigger our BffClient client code to do its behavior 
        // we configured it to use the mock instead of needing some external thing to run
        const sut= new BffClient(mockserver.url, "");
        const response = sut.get()
        
        // Assert: check the result
        expect(response.status).toEqual(200)
        const data: any[] = await response.json()
        expect(data).toEqual([exampleData]);
    });
});

Contract Testing helps you deploy faster with more confidence

Many modern systems are made-up of lots of small components such a microservices and frontends. Each independently built, released and maintained by a bunch of different teams. This is really useful for scaling out your organisation and increasing the speed at which changes can be delivered to your customers.

For people involved in producing this kind of software, it poses a few challenges:

  1. Will a new version of component X still work with its neighboring components?
  2. What does component Y even expect of component X? What is the structure of the data X returns? What status codes and headers?
  3. What version of component X is running in which environment. What version of the interface does X have there?
  4. How do we start work on X even though we have no clue when Y will be available?
  5. If I write a mock for Y does it truly simulate its interface or am I blinded by my own assumptions?
  6. How can I quickly deploy and test X without having to spin up its neighboring components? Can I even achieve that in a large product?

Contract Testing solves these problems for you. Lets assume we have some micro-service X and a web front-end Y that communicates with it. The tools that implement Contract Testing let component Y document its expectations of the interface with X in a machine readable contract, frequently called a PACT.

How it works from Y’s point-of-view:

The unit/integration tests of Y define the PACT. Maybe its the same as the previous version, maybe it different. Both is fine.

They let the test framework dynamically generate a mock for X based on this PACT. Instead of needing to talking to a ‘real’ X, they talk to the mock.

The mock and Y’s own unit test together verify that Y really works according to the PACT. If the test fails, the team of Y has some fixing work to do and this process restarts. If the test passes, Y uploads the PACT to a broker and informs it which version of Y just validated successfully.

Whenever Y is deployed to production its CI/CD pipeline asks the broker if the current version of X in production has been validated against this PACT version. If not, Y’s deployment fails. If its fine, the pipeline informs the broker which version of Y is now deployed to production.

How it works from X’s point-of-view:

The unit/integration tests of X Start an instance of X running locally on some reachable network location.

It retrieves the PACT from the broker and uses the testing framework to generate a mock of Y. This mock sends requests to X based on the PACT and validates X’s responses match the PACT.

If the test fails, the team of X has some fixing work to do and this process restarts. If the test passed, X communicates to the broker that it was able to work with this version of the PACT.

Whenever X is deployed to production its CI/CD pipeline asks the broker if the current version of Y in production has been validated against this PACT version. If not, X’s deployment fails. If its fine, the pipeline informs the broker which version of X is now deployed to production.

Using defineParameterType() with multiple regexes in cucumber-js

In a previous post I showed how to automatically replace parameter values before they get passed to your step definition code.

Now lets say you want that replacing/parsing/transformation for single- and double-quoted string like this in your feature file

When I print "Hello world!"
When I print 'its a beautiful day!'

Well…you’re in luck! The defineParameterType() method allows you to pass an array of regexes. We can use that to support both single- and double quoted strings with the same transformation function.

There’s a big gotcha here though. The the docs say this about the transformation function

A function or method that transforms the match from the regexp. Must have arity 1 if the regexp doesn’t have any capture groups. Otherwise the arity must match the number of capture groups in regexp.

In other words, when you use an array of regexes or if your regex has multiple capture groups, the function must have the same number of parameters as the total number of capture groups of all regexes in the array.

When cucumber calls the function, each parameter contains result from the corresponding capture group in the regex/array. If a regex does not match then cucumber passes an undefined value to the corresponding parameter number of the transform function So you’ll have to check each element if its undefined/null or not before using it.

defineParameterType({
    regexp: [/'([^']*)'/, /"([^"]*)"/],
    transformer: function (singleQ, doubleQ) {
        return singleQ ? replacePlaceholders(singleQ) : replacePlaceholders(doubleQ)
    },
    name: "a_string_with_replaced_placeholders"
});

Automatic type conversion of parameters in SpecFlow

In a previous post I showed that SpecFlow can change values of parameters. This mechanism is not just limited to transforming content of string values. It can also convert the literal string in your feature file to some complex object for your step-definition code.

Let say in your feature file you want to write steps like this:

Scenario: MyScenario
    When I print this list 'A,B,C,D'

Then you might be tempted to convert the string 'A,B,C,D' to a list like this

[Binding]
public class MyBindings
{
    [StepDefinition(@"I print this list '([^']*)'") ]
    public void PrintList(string input)
    {
        var items = input.Split(',')
        foreach(var item in items)
        {
            Console.WriteLine(item)
        }
    }
}

Don’t do this…it causes the same problems as mentioned in the previous post.

Instead SpecFlow’s Step Argument Conversion lets us simplify our step definition code to this:

[Binding]
public class MyBindings
{
    [StepDefinition(@"I print this list '([^']*)'") ]
    public void PrintList(IEnumerable<string> items) 
    {
        foreach(var item in items)
        {
            Console.WriteLine(item)
        }
    }
   
    [StepArgumentTransformation]
    public IEnumerable<string> TransformStringToList(string input)
    {
        return input.Split(',');
    }
}

Automatically replacing/transforming input parameters in cucumber-js

Most implementations of cucumber provide a mechanism for changing literal text in the feature file to values or objects your step definition code can use. This is known as step definition or step argument transforms. Here’s how this works in cucumber-js.

Assume we have this scenario:

Scenario: Test
    When I print 'Welcome {myname}'
    And I print 'Today is {todays_date}'

And we have this step-definition.

defineStep("I print {mystring}", async function (this: OurWorld, x: string) {
    console.log(x)
});

Notice the use of {mystring} in the Cucumber expression

We can use defineParameterType() to automatically replace all placeholders.

defineParameterType({
    regexp: /'([^']*)'/,
    transformer: function (s) {
        return s
            .replace('{todays_date}', new Date().toDateString())
            .replace('{myname}', 'Gerben')
    },
    name: "mystring",
    useForSnippets: false
});

You can even use this to for objects like so:

defineParameterType({
    name: 'color',
    regexp: /red|blue|yellow/,
    transformer: s => new Color(s)
})

defineStep("I fill the canvas with the color {color}", async function (this: OurWorld, x: Color) {
    // x is an object of type Color
});

When I fill the canvas with the color red

Containerising the development environment

One of the nice things about docker is that we can use all kinds of software without cluttering up our local machine. I really like the ability to have the development environment running in a container. Here is an example where we:

  • Get a Node.js development environment with all required tools and packages
  • Allow remote debugging of the app in the container
  • See code changes immediately reflected inside the container

The dockerfile below gives us a container with all required tools and packages for a Node.js app. In this example we assume the ‘.’ directory contains the files needed to run the app.

FROM node:9

WORKDIR /code

RUN npm install -g nodemon

COPY package.json /code/package.json
RUN npm install && npm ls
RUN mv /code/node_modules /node_modules
COPY . /code

CMD ["npm", "start"]

That’s nice, but how does this provide remote debugging? and how do code changes propagate to a running container?

Two very normal aspects of docker achieve this. Firstly docker-compose.yml overrules the CMD ["npm", "start"] statement to start nodemon with the --inspect=0.0.0.5858 flag. That starts the app with the debugger listening on all of the machines IP addresses. We expose port 5858 to allow remote debuggers to connect to the app in the container.

Secondly, the compose file contains a volume mapping that overrules the /code folder in the container and points it to the directory on the local machine where you edit the code. Combined with the --watch flag nodemon sees any changes you make to the code and restarts the app in the container with the latest code changes.

Note: If you are running docker on Windows of the code is stored on some network share, then you must use the --legacy-watch flag instead of --watch

The docker-compose.yml file:

version: "2"

services:
  app:
    build: .
    command: nodemon --inspect=0.0.0.0:5858 --watch
    volumes:
      - ./:/code
    ports:
      - "5858:5858"

Here’s a launch.json for Visual Studio Code to attach to the container.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach",
            "type": "node",
            "request": "attach",
            "port": 5858,
            "address": "localhost",
            "restart": true,
            "sourceMaps": false,
            "outDir": null,
            "localRoot": "${workspaceRoot}",
            "remoteRoot": "/code"
        }
    ]
}

Understanding the LINQ nested grouping example

Here’s an explanation of how the default example for LINQ nested grouping actually works. The usual example for nested grouping looks like this:

from student in Students
group student by student.Faculty into Faculty
from dbtgroup in
(
    from student in Faculty
    group student by student.DebtCategory
)
group dbtgroup by Faculty.Key;

The objective of this statement is to first group-by students into faculties and then in each faculty create subgroupings of students by their DebtCategory.

So how does this actually work and whats the equivalent method/lamba syntax? The first step is to groups each student into their faculty. Assume we have the following data

public class Student
{
   public string Name { get; set; }
   public string Faculty { get; set; }
   public int DebtCategory { get; set; }
}

IList<Student> Students = new List<Student>();
Students.Add(new Student { Name = "John" , Faculty = "IT"     , DebtCategory = 2 });
Students.Add(new Student { Name = "Jane" , Faculty = "IT"     , DebtCategory = 2 });
Students.Add(new Student { Name = "Jesse", Faculty = "Finance", DebtCategory = 2 });
Students.Add(new Student { Name = "Linda", Faculty = "Finance", DebtCategory = 1 });

The following query groups each student into a faculty

var query1 = from student in Students
group student by student.Faculty into Faculty
select Faculty;

//The Method syntax for the above query is:
var query1Method = Students
.GroupBy(student => student.Faculty)
.Select ( Faculty => Faculty);

//This gives us the following IGrouping<string, Student> as result
//
// [0]
//    Key   :  IT
//    Values: 
//          [0] John (IT) (2)
//          [1] Jane (IT) (2)
//
// [1]
//    Key   : Finance
//    Values:
//          [0] Jesse (Finance) (2)
//          [1] Linda (Finance) (1)

The next step is to add another level of grouping:

var query2 = from student in Students
group student by student.Faculty into Faculty
from dbtgroup in
(
    from student in Faculty
    group student by student.DebtCategory
)
select dbtgroup;
//This gives us the following IGrouping<int, Student> as result
//[0]
//  Key   : 2
//  Values:
//        [0] John (IT) (2)
//        [1] Jane (IT) (2)
//
//[1]
//  Key   : 2
//  Values:
//        [0] Jesse (Finance) (2)
//
//[2]
//  Key   : 1
//  Values:
//        [0] Linda (Finance) (1)

// The following is the literal translation of the above Comprehension syntax into method syntax. We're ignoring this as explained below
//	var query2Method = Students
//		.GroupBy(student => student.Faculty)
//		.SelectMany(  Faculty =>Faculty.GroupBy(student => student.DebtCategory)
//					, (Faculty, dbtgroup) => dbtgroup);
	
//The final complete query ends with"group dbtgroup by Faculty.Key;" 
// this statement causes the compiler to see that you're refering to the Faculty object from the select many, so instead of 
// "(Faculty, dbtgroup) => dbtgroup" it emits a slightly different projection "(Faculty, dbtgroup) => new {Faculty, dbtgroup}
//structure
var query2Method = Students
.GroupBy(student => student.Faculty)
.SelectMany( Faculty =>Faculty.GroupBy(student => student.DebtCategory)
	 , (Faculty, dbtgroup) => new {Faculty, dbtgroup});

Query2 is close to our desired output, however the grouping is the wrong way around. So the final step is:

var query3 = from student in Students
group student by student.Faculty into Faculty
from dbtgroup in
    (
    from student in Faculty
    group student by student.DebtCategory
    )
group dbtgroup by Faculty.Key;

//The method/lambda syntax is:
var query3Method = Students
.GroupBy(student => student.Faculty)
.SelectMany (
	Faculties => Faculties.GroupBy (student => student.DebtCategory)
	, (Faculty, dbtgroup) => 
		new  
		{
			Faculty = Faculty, 
			dbtgroup = dbtgroup
		} )
.GroupBy( item => item.Faculty.Key, item => item.dbtgroup );

//This gives us the following groups as result
//[0]
//  Key   : IT
//  Values:
//        [0] Key   : 2
//            Values:
//                  [0] John (IT) (2)
//                  [1] Jane (IT) (2)
//[1]
//  Key   : Finance
//  Values:
//        [0] Key   : 2
//            Values:
//                  [0] Jesse (Finance) (2)
//        [1] Key   : 1
//            Values:
//                    [0] Linda (Finance) (1)