Combining duplicate step definitions

In a similar vein, we can introduce parameters into our step definitions to help us avoid duplicated code. With string patterns, parameters can be specified using curly braces ({}), inside of which the type of the variable is indicated.

For example, our Then our API should respond with a <statusCode> HTTP status code step definition can be redefined as follows:

Then('our API should respond with a {int} HTTP status code', function (statusCode) {
assert.equal(this.response.statusCode, statusCode);
});

Here, we've replaced the hardcoded 400 HTTP status code with a placeholder, {int}, which indicates that the pattern should match an integer. Then, we are passing the value of the placeholder into the code function as statusCode, which is then used to perform the checks.

We can do the same with regular expression patterns. Instead of curly braces, we can define parameters by adding capturing groups to the RegEx. For instance, the same step definition would look like this using a regular expression pattern:

Then(/^our API should respond with a ([1-5]\d{2}) HTTP status code$/, function (statusCode) {
assert.equal(this.response.statusCode, statusCode);
});

Update your spec/cucumber/steps/index.js file to add groups to the regular expression patterns, and use those captured parameters in your step definition function. The end result should look like this:

import assert from 'assert';
import superagent from 'superagent';
import { When, Then } from 'cucumber';

When(/^the client creates a (GET|POST|PATCH|PUT|DELETE|OPTIONS|HEAD) request to ([/\w-:.]+)$/, function (method, path) {
this.request = superagent(method, `${process.env.SERVER_HOSTNAME}:${process.env.SERVER_PORT}${path}`);
});

When(/^attaches a generic (.+) payload$/, function (payloadType) {
switch (payloadType) {
case 'malformed':
this.request
.send('{"email": "dan@danyll.com", name: }')
.set('Content-Type', 'application/json');
break;
case 'non-JSON':
this.request
.send('<?xml version="1.0" encoding="UTF-8" ?><email>dan@danyll.com</email>')
.set('Content-Type', 'text/xml');
break;
case 'empty':
default:
}
});

When(/^sends the request$/, function (callback) {
this.request
.then((response) => {
this.response = response.res;
callback();
})
.catch((error) => {
this.response = error.response;
callback();
});
});

Then(/^our API should respond with a ([1-5]\d{2}) HTTP status code$/, function (statusCode) {
assert.equal(this.response.statusCode, statusCode);
});

Then(/^the payload of the response should be a JSON object$/, function () {
// Check Content-Type header
const contentType = this.response.headers['Content-Type'] || this.response.headers['content-type'];
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Response not of Content-Type application/json');
}

// Check it is valid JSON
try {
this.responsePayload = JSON.parse(this.response.text);
} catch (e) {
throw new Error('Response not a valid JSON object');
}
});

Then(/^contains a message property which says (?:"|')(.*)(?:"|')$/, function (message) {
assert.equal(this.responsePayload.message, message);
});