Shouty
is a social network that allows people who are physically close to communicate, just like people have always communicated with their voices. In the real world you can talk to someone in the same room, or across the street. Or even 100 m away from you in a park - if you shout.
That’s Shouty. What you say on this social network can only be “heard” by people who are nearby.
Let’s start with a very basic example of Shouty’s behaviour.
Sean the shouter shouts "free bagels at Sean’s" and Lucy the listener who happens to be stood across the street from his store, 15 metres away, hears him. She walks into Sean’s Coffee and takes advantage of the offer.
We can translate this into a Gherkin scenario so that Cucumber can run it. Here’s how that would look.
Scenario: Listener is within range
Given Lucy is located 15m from Sean
When Sean shouts "free bagels at Sean's"
Then Lucy hears Sean’s message
Scenario just tells Cucumber we’re about to describe an example that it can execute.
Given
is the context for the scenario. We’re putting the system into a specific state, ready for the scenario to unfold.
When
is an action. Something that happens to the system that will cause something else to happen: an outcome.
Then
is the outcome. It’s the behaviour we expect from the system when this action happens in this context.
You’ll notice we’ve omitted from our outcome anything about Lucy walking into Sean’s store and making a purchase. Remember, Gherkin is supposed to describe the behaviour of the system, so it would be distracting to have it in our scenario.
Each scenario has these three ingredients: a context
, an action
, and one or more outcomes
.
Together, they describe one single aspect of the behaviour of the system. An example.
Now that we’ve translated our example into Gherkin, we can automate it!
First we’ll create a package.json that describes the NPM packages we need for our project and add the @cucumber/cucumber package to it.
$ npm init -y
$ npm install -D @cucumber/cucumber
We can now run the cucumber-js command to see if everything works:
$ ./node_modules/.bin/cucumber-js
There’s no output, because we haven’t written any scenarios yet, but there are no errors either! Having to remember the full path to Cucumber is not very user friendly, but luckily there’s a nicer way.
Edit package.json and change the test line under the scripts from it’s default to cucumber-js
"scripts": {
"test": "cucumber-js"
},
Now we’re ready! If we run npm test at this point, we’ll again see cucumber pass with no errors.
>> npm test
Failure
1 scenario (1 undefined)
Undefined
means Cucumber doesn’t know what to do for any of the three steps we wrote in our Gherkin scenario. It needs us to provide some step definitions
.
Step definitions
translate from the plain language you use in Gherkin into JavaScript code.
When Cucumber runs a step, it looks for a step definition that matches the text in the step. If it finds one, then it executes the code in the step definition.
If it doesn’t find one… well, you’ve just seen what happens. Cucumber helpfully prints out some code snippets that we can use as a basis for new step definitions.
Create a new directory called step_definitions underneath the features directory, Say: steps.js.
steps.js
const { Given, When, Then } = require('@cucumber/cucumber')
Given('Lucy is located {int} metres from Sean', function (int) {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
When('Sean shouts {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
Then('Lucy hears Sean\'s message', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
Q. What does it mean when Cucumber says a step is Pending?
- Cucumber tells us that a step (and by inference the Scenario that contains it) is Pending when the automation code throws a Pending error.
- This allows the development team to signal that automation for a step is a work in progress. This makes it possible to tell the difference between steps that are still being worked on and steps that are failing due to a defect in the system.
- For example, when we run our tests in a Continuous Integration (CI) environment, we can choose to ignore pending scenarios.
steps.js
Given('Lucy is located {int}m from Sean', function (distance) {
this.lucy = new Person
this.sean = new Person
this.lucy.moveTo(distance)
})
When('Sean shouts {string}', function (message) {
this.sean.shout(message)
this.message = message
})
Then('Lucy hears Sean’s message', function () {
assert.deepEqual(this.lucy.messagesHeard(), [this.message])
})
shouty.js
class Person {
moveTo() {
}
shout(message) {
}
messagesHeard() {
return ["free bagels at Sean's"]
}
}
- First we specified the behaviour we wanted, using a Gherkin scenario in a feature file.
- Then we wrote step definitions to translate the plain english from our scenario into concrete actions in code.
- Finally, we used the step definitions to guide us in building out our very basic domain model for the Shouty application.