Filed under: Cucumber, — Tags: Behaviour-Driven Development, Cucumber-js, Gherkin, JavaScript — Thomas Sundberg — 2018-02-07
I usually describe myself as a Java backend developer. That is actually not completely true. I tend not to shy away from the frontend, but I prefer the backend because it is usually much easier to test.
This backend focus has allowed me to almost entirely miss out on JavaScript development. I can't say that I mind that much. I use JavaScript when a web page needs it, but I use Google a lot to find examples I can modify to fit my needs.
One thing I am curious about though is to be able to use Cucumber-js. I have never done it before so this a blog post that will describe my experience getting started.
I share this experience because one way for me to learn is to teach. I don't have a student this time, I have a blog post. A blog post will, as a group of students, force me to be as clear as I can be. And before I publish it, I will have it reviewed by someone who knows the topic as well as a novice or two.
I want to reduce the number of tools I use. Using too many tools will be confusing and move focus from
the actual goal, getting Cucumber-js
up and running.
This means that I will do everything from a a command line. You need an editor, any editor will be fine. I use InteliJ IDEA as my general purpose editor. You may have another favorite. Use an editor you are comfortable with.
Cucumber-js
depends on node.js
.
My first step is to install node.js
. There are different ways to do this. I'm on a Mac so I use
Homebrew. All I had to do was brew install node
in a terminal. You may
have to do something else
on your operating system.
To verify that I have node.js
installed properly, I type node -v
in a terminal.
$ node -v
v9.4.0
This tells me that I have node.js
installed, The latest version as I write this is 9.4.0. I'm up to
date.
I also need a JavaScript package manager called npm
. It should be
installed when node.js
is installed. To verify that npm
is installed and check its version I do npm -v
$ npm -v
5.6.0
Looks good, I should be ready to install Cucumber-js
now.
Before installing Cucumber-js
, I need a working directory for the rest of this tutorial.
Create a new directory called cucumber-js
and change into it. The rest of tutorial assumes that
you are in this directory.
Cucumber-js
is available as an npm package. This allows me to install it using npm install
cucumber
.
$ npm install cucumber
> cucumber-expressions@5.0.13 postinstall /Users/tsu/cucumber-js/node_modules/cucumber-expressions
> node scripts/postinstall.js
npm WARN saveError ENOENT: no such file or directory, open '/Users/tsu/cucumber-js/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/Users/tsu/cucumber-js/package.json'
npm WARN example No description
npm WARN example No repository field.
npm WARN example No README data
npm WARN example No license field.
+ cucumber@4.0.0
added 66 packages in 2.483s
A lot of things happened here. I'm not entirely sure about the details. But the Cucumber documentation tells me that
I should be able to run Cucumber if I do ./node_modules/.bin/cucumber-js
The result is:
$ ./node_modules/.bin/cucumber-js
0 scenarios
0 steps
0m00.000s
Nothing bad happened and it was quick. I guess that's a good start.
With an environment prepared and verified, it's time to create a first scenario.
Scenarios live in the directory features
. My first scenario is a really boring one, adding two numbers.
It is boring
but it allows me to focus on getting the infrastructure up and running. And that is the main purpose with this
exercise.
I create the file features/addition.feature
with this content:
Feature: Addition
Addition is great as a verification exercise to get the Cucumber-js infrastructure up and running
Scenario: Add two number
Given the numbers 1 and 3
When they are added together
Then should the result be 4
Next step is to run Cucumber and see what happens. I expect some hints on creating steps.
$ ./node_modules/.bin/cucumber-js
UUU
Warnings:
1) Scenario: Add two number # features/addition.feature:5
? Given the numbers 1 and 3
Undefined. Implement with the following snippet:
Given('the numbers {int} and {int}', function (int, int2, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
? When they are added together
Undefined. Implement with the following snippet:
When('they are added together', function (callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
? Then should the result be 4
Undefined. Implement with the following snippet:
Then('should the result be {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
1 scenario (1 undefined)
3 steps (3 undefined)
As a JavaScript newbie but not a Cucumber newbie, I got pretty much what I expected. I got UUU
which I
assume
means that there are three undefined steps. I also got three snippet suggestions.
My next task is to implement the missing steps. Looking at examples on GitHub, it seems as if the convention is to
store the steps in a directory called features/step_definitions/
. I create the file
features/step_definitions/addition_steps.js
with the snippets suggested.
The result looks like this:
Given('the numbers {int} and {int}', function (int, int2, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
When('they are added together', function (callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Then('should the result be {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Running Cucumber again gives me this:
$ ./node_modules/.bin/cucumber-js
ReferenceError: Given is not defined
at Object.<anonymous> (/Users/tsu/cucumber-js/features/step_definitions/addition_steps.js:1:63)
at Module._compile (module.js:660:30)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
at Module.require (module.js:604:17)
at require (internal/module.js:11:18)
at /Users/tsu/cucumber-js/node_modules/cucumber/lib/cli/index.js:163:16
at Array.forEach (<anonymous>)
at Cli.getSupportCodeLibrary (/Users/tsu/cucumber-js/node_modules/cucumber/lib/cli/index.js:162:24)
at Cli.<anonymous> (/Users/tsu/cucumber-js/node_modules/cucumber/lib/cli/index.js:181:39)
at Generator.next (<anonymous>)
at Generator.tryCatcher (/Users/tsu/cucumber-js/node_modules/bluebird/js/release/util.js:16:23)
at PromiseSpawn._promiseFulfilled (/Users/tsu/cucumber-js/node_modules/bluebird/js/release/generators.js:97:49)
at Promise._settlePromise (/Users/tsu/cucumber-js/node_modules/bluebird/js/release/promise.js:574:26)
Something is clearly missing here. With a Java background, I have the feeling that I need to make some dependencies available. How do you do that?
By adding the line const { Before, Given, When, Then } = require('cucumber')
at the top of my JavaScript
file I seem
to be able to define the Gherkin keywords that were not defined.
features/step_definitions/addition_steps.js
now looks like this:
const { Before, Given, When, Then } = require('cucumber')
Given('the numbers {int} and {int}', function (int, int2, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
When('they are added together', function (callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Then('should the result be {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Running Cucumber again:
$ ./node_modules/.bin/cucumber-js
P--
Warnings:
1) Scenario: Add two number # features/addition.feature:5
? Given the numbers 1 and 3 # features/step_definitions/addition_steps.js:3
Pending
- When they are added together # features/step_definitions/addition_steps.js:9
- Then should the result be 4 # features/step_definitions/addition_steps.js:14
1 scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m00.002s
This is much better. It doesn't work, but now I'm getting a P
which I am sure means that there is a pending
step.
A pending step means that there is a step found, but that it isn't properly implemented yet. Next task is to implement this pending step.
The first step should prepare some kind of calculator and feed it two numbers.
I will create a lib
directory at the same level as the feature
directory and add a file
called calculator.js
in
it that will contain a calculator.
My calculator.js
now looks like this:
class Calculator {
}
module.exports = Calculator;
As you can see, it is currently just an empty skeleton. I want to make the Calculator
available to
my steps.
I do that by require the Calculator
in my steps implementations. My steps have now evolved to this:
const { Before, Given, When, Then } = require('cucumber');
const Calculator = require('../../lib/calculator');
let calculator;
Given('the numbers {int} and {int}', function (int, int2, callback) {
calculator = new Calculator();
callback();
});
When('they are added together', function (callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Then('should the result be {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
I require calculator.js
and define a constant Calculator. This the equivalent of an import
in Java.
I also declare a variable called calculator
to keep an instance of a calculator between the steps.
Finally, in the Given
step, I create a new Calculator and assign it to the field
Running Cucumber again gives me this result:
$ ./node_modules/.bin/cucumber-js
.P-
Warnings:
1) Scenario: Add two number # features/addition.feature:5
✔ Given the numbers 1 and 3 # features/step_definitions/addition_steps.js:6
? When they are added together # features/step_definitions/addition_steps.js:11
Pending
- Then should the result be 4 # features/step_definitions/addition_steps.js:16
1 scenario (1 pending)
3 steps (1 pending, 1 skipped, 1 passed)
0m00.003s
The first step passes. The second step is now pending. I'm not done with the first step, but I seem to be able to run Cucumber without errors. That's a good start!
My next step is to declare two variables in the calculator and assign them in the constructor.
I'll call the variables x
and y
The calculator now looks like this:
class Calculator {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
module.exports = Calculator;
I have a constructor that accepts two values. These two values are used in the Given
step. The steps now
looks like this:
const { Before, Given, When, Then } = require('cucumber');
const Calculator = require('../../lib/calculator');
let calculator;
Given('the numbers {int} and {int}', function (x, y, callback) {
calculator = new Calculator(x, y);
callback();
});
When('they are added together', function (callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Then('should the result be {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
I used the new constructor and I renamed the parameters x
and y
.
This should be enough for my first step. Next up is to clean the pending steps and implement the second step, using the calculator.
My first action is to clean up a bit. My tests are passing so I can do changes without the risk of introducing
problems. I would like to get rid of the pending steps. I also don't understand what the callback
parameter is used for. What happens if I just remove it? Some experimenting ended up with me removing callback
parameter. This resulted in two things, a bit cleaner code and the pending steps removed.
My steps now look like this:
const { Before, Given, When, Then } = require('cucumber');
const Calculator = require('../../lib/calculator');
let calculator;
Given('the numbers {int} and {int}', function (x, y) {
calculator = new Calculator(x, y);
});
When('they are added together', function () {
});
Then('should the result be {int}', function (int) {
});
This looks much better to me. I didn't really understand what the parameter callback
was used for. My
current
understanding is that it was used for reporting back to Cucumber that this step is pending.
After feedback I have learned that the callback
is the original way of dealing with non-blocking
asynchronous operations in Node.js
and was the original interface to step definitions. The different
interfaces
supported nowadays are documented here:
https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/step_definitions.md
It can be argued that the cleaning I did at this stage is premature. It may be better to keep the pending steps
until the they are properly implemented. Cleaning this up early can trick a beginner to believe we're done. I will
mitigate this risk by deliberately fail the execution before I'm done and make sure that the failure is what I
expected. I will do this in my last step, the Then
step that is empty above.
Running Cucumber now gives me this:
$ ./node_modules/.bin/cucumber-js
...
1 scenario (1 passed)
3 steps (3 passed)
0m00.000s
This is great, I have a passing build! It is just a bit unfortunate that I know that it's just an empty execution. We don't do anything useful yet.
Let's make one useful thing, lets use the calculator so we can assert that a calculation actually was done in the last step.
I call calculator.add()
in the When
step
const { Before, Given, When, Then } = require('cucumber');
const Calculator = require('../../lib/calculator');
let calculator;
Given('the numbers {int} and {int}', function (x, y) {
calculator = new Calculator(x, y);
});
When('they are added together', function () {
calculator.add();
});
Then('should the result be {int}', function (int) {
});
I also implement the corresponding add()
in the calculator
. My new Calculator
looks like this:
class Calculator {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
this.result = this.x + this.y;
}
}
module.exports = Calculator;
Running cucumber is unchanged, everything passes. But I know that there is no assert yet. Lets add a verification of the result in the last step.
The first thing I do is to add getting the result in the Then
step. I also need to make
assert
available to my
steps. The last version of the steps look like this:
const assert = require('assert')
const {Before, Given, When, Then} = require('cucumber');
const Calculator = require('../../lib/calculator');
let calculator;
Given('the numbers {int} and {int}', function (x, y) {
calculator = new Calculator(x, y);
});
When('they are added together', function () {
calculator.add();
});
Then('should the result be {int}', function (expected) {
assert.equal(calculator.getResult(), expected)
});
This forces me to implement the last part in the calculator, retrieving the result. The calculator now becomes:
class Calculator {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
this.result = this.x + this.y;
}
getResult() {
return this.result;
}
}
module.exports = Calculator;
Running Cucumber still looks good:
$ ./node_modules/.bin/cucumber-js
...
1 scenario (1 passed)
3 steps (3 passed)
0m00.001s
There is no difference compared to the last execution. How can we be sure that it works? I never trust a test I haven't seen fail. Let's fail the execution verify that it is actually comparing the calculated value with the expected value.
The simplest way I can think of is to change the feature. Let's expect 5 instead of 4. The feature will look like this now:
Feature: Addition
Addition is great as a verification exercise to get the Cucumber-js infrastructure up and running
Scenario: Add two number
Given the numbers 1 and 3
When they are added together
Then should the result be 5
Running cucumber now looks like this:
$ ./node_modules/.bin/cucumber-js
..F
Failures:
1) Scenario: Add two number # features/addition.feature:5
✔ Given the numbers 1 and 3 # features/step_definitions/addition_steps.js:7
✔ When they are added together # features/step_definitions/addition_steps.js:11
✖ Then should the result be 5 # features/step_definitions/addition_steps.js:15
AssertionError [ERR_ASSERTION]: 4 == 5
+ expected - actual
-4
+5
at World.<anonymous> (/Users/tsu/cucumber-js/features/step_definitions/addition_steps.js:16:12)
1 scenario (1 failed)
3 steps (1 failed, 2 passed)
0m00.001s
We have a failure. This failure is a success as it show that the value in the Then
step actually is
used.
Change it back and run Cucumber again. You should now have a passing scenario.
If you have followed along this far, you have seen how a seasoned Java developer was able to get Cucumber up and running. I must admit that I have searched the Internet for examples before I came up with this solution. I have also looked at some material supplied by Cucumber Ltd.
I would like to thank
for feedback and finding typos I missed.
Thank you very much!
A reviewer wondered, how do you put this under version control? All serious development is done using version control. This is very a valid question.
The consultant answer is it depends. I use git
for version control. With git
you do:
git init
- initiate a new version controlled projectgit add
- add all filesgit commit
- commit the added files to version controlMore details are out of the scope in this tutorial. Better tutorials can be found using Google.