- 1. JavaScript testing #1. Explaining types of tests. Basics of unit testing with Jest
- 2. JavaScript testing #2. Introducing Enzyme and testing React components
- 3. JavaScript testing #3. Testing props, the mount function and snapshot tests.
- 4. JavaScript testing #4. Mocking API calls and simulating React components interactions
- 5. JavaScript testing #5. Testing hooks with react-hooks-testing-library and Redux
- 6. JavaScript testing #6. Introduction to End-to-End testing with Cypress
- 7. JavaScript testing #7. Diving deeper into commands and selectors in Cypress
- 8. JavaScript testing #8. Integrating Cypress with Cucumber and Gherkin
- 9. JavaScript testing #9. Replacing Enzyme with React Testing Library
- 10. JavaScript testing #10. Advanced mocking with Jest and React Testing Library
- 11. JavaScript testing #11. Spying on functions. Pitfalls of not resetting Jest mocks
- 12. JavaScript testing #12. Testing downloaded files and file inputs with Cypress
- 13. JavaScript testing #13. Mocking a REST API with the Mock Service Worker
- 14. JavaScript testing #14. Mocking WebSockets using the mock-socket library
Cypress has proven to be an appropriate tool for writing E2E tests. In the previous parts of this series, we’ve gone through its basics. That’s a proper moment to take a look at a useful addition: the Gherkin syntax. In this article, we look into its principles and how to make a Cypress Cucumber integration.
When testing our application, it is very beneficial to simulate the behavior of our users. A proper approach is to define scenarios that describe the system from the customer’s point of view. Doing so is incredibly useful. Such scenarios are helpful when working with team members having less technical knowledge, stakeholders, and product owners.
Making a Cypress Cucumber integration
A reliable way of doing the above is to write in the Gherkin syntax. It comes from the Cucumber tool and is designed to be easily readable even for non-technical people. It promotes Behaviour-driven development in its core. Most importantly, it formalizes an understanding of how the flow of the application should look like.
Let’s start by installing a preprocessor that we need to use the Gherkin syntax:
1 |
npm install cypress-cucumber-preprocessor |
As noted in one of the previous parts of this series, the plugins directory contains files that aim to extend and modify the behavior of Cypress. Therefore, it is a proper place to include cypress-cucumber-preprocessor.
cypress/plugins/index.js
1 2 3 4 5 |
const cucumber = require('cypress-cucumber-preprocessor').default; module.exports = (on) => { on('file:preprocessor', cucumber()) }; |
The second thing to do is to modify the cypress.json file:
1 2 3 |
{ "testFiles": "**/*.feature" } |
The above is caused by the fact that we use Gherkin syntax in files with the .feature extension.
To avoid making our step definitions global, we also add this configuration to our package.json:
1 2 3 |
"cypress-cucumber-preprocessor": { "nonGlobalStepDefinitions": true } |
This above is going to become the default option in upcoming versions of cypress-cucumber-preprocessor
There seem to bee some performance issues on Windows. If that’s a concern for you, check out this issue.
The principles of the Gherkin syntax
Gherkin is a language operating using a set of keywords. With them, we structure our .feature files. This is an example of such file:
1 2 3 4 5 6 |
Feature: Search functionality Scenario: Using the search input Given I am on the homepage When I fill the search input with the "JavaScript" term And I click on the submit button Then I should be redirected to a search page with the results of the "JavaScript" search |
Let’s look into various keywords and their meanings.
Feature
The Feature keyword provides a short description of a tested feature. It has to be the first keyword used in a .feature file. We can follow it with an additional description below, but we don’t have to.
1 2 |
Feature: Search functionality This feature allows searching through articles using a string |
Scenario
A scenario is an example that represents a business rule. That is to say, it serves as a specification of the application. A scenario consists of multiple steps.
1 |
Scenario: Using the search input |
Use meaningful names for scenarios and features to increase readability. Moreover, use additional descriptions if one line is not enough
Given
The first type of step is called a Given. Its purpose is to define the context of a scenario and therefore put the system into a specific state.
1 |
Given I am on the homepage |
1 |
Given I am authenticated |
When
The When step describes an action. Therefore, it is a fitting place to represent actions taken by the users of our application.
1 |
When I fill the search input with the "JavaScript" term |
1 |
When I add a book "You Don't Know JS" into the shopping cart |
Then
Then keyword represents a step verifying an outcome of the actions. By using it, we can observe changes in the interface and verify if they are correct.
1 |
Then I should be redirected to a search page with the results of the "JavaScript" search |
1 |
Then the book "You don't know JS" should be visible on the list in the shopping cart |
And & But
We often find ourselves in need of writing multiple steps of the same type in a row. Instead of doing so, we can use keywords And & But.
1 2 |
When I fill the search input with the "JavaScript" term And I click on the submit button |
1 2 |
Then the book "You don't know JS" should be visible on the list in the shopping cart But I should be able to add more books |
Background
One feature can have multiple scenarios. If every one of them is in the same context and repeats the same Given steps, we can put them in the Background statement.
1 2 3 4 5 6 7 8 9 10 11 |
Feature: Managing blog posts Background: Given I am on the admin page And I am authenticated Scenario: Adding a blog post # ... Scenario: Removing a blog post # ... |
Using Gherkin with Cypress
Since we are familiar with the Gherkin syntax, we can now start using it with Cypress. Aside from using .feature files, we need to describe every step using JavaScript.
The recommended way of structuring files is having one directory per a feature file.
1 2 3 4 5 6 7 8 9 10 11 |
├── cypress │ ├── fixtures │ ├── integration │ │ ├── Search │ │ │ └── Search.js │ │ ├── Search.feature │ ├── plugins │ │ └── index.js │ └── support │ ├── commands.js │ └── index.js |
The directory needs to have the same name as the feature file. You can name the files in the directory in any way you like.
After importing the desired step from cypress-cucumber-preprocessor/steps, we can use it to describe actions.
1 2 3 4 5 |
import { Given } from 'cypress-cucumber-preprocessor/steps'; Given('I am on the homepage', () => { cy.visit(''); }); |
Thanks to doing the above, we associate a piece of JavaScript code with a particular Gherkin command.
Parameters
We can also pass arguments to our Gherkin expressions. To do that, we use curly brackets with the type of the argument.
1 |
When I fill the search input with the "JavaScript" term |
1 2 3 |
When('I fill the search input with the {string} term', (term) => { cy.get('.search-field').type(term); }); |
Aside from using just a regular string type, we can define custom types and use them in our feature files.
Sharing context
Even though every step definition is a separate function, we can share some context between them. To do so, we can use the “as” command.
1 2 |
Then the book "You don't know JS" should be visible on the list in the shopping cart And I should be able to add more items of the same type |
1 2 3 4 5 6 7 8 9 |
import { Then, And } from 'cypress-cucumber-preprocessor/steps'; Then('the book {string} should be visible on the list in the shopping cart', (bookName) => { cy.get('div[data-testid="shoppingCart"]').contains(bookName).as('addedBook'); }); And('I should be able to add more items of the same type', () => { cy.get('@addedBook').find('button[data-testid="addButton"]') }); |
Common steps
There are often steps that we would like to reuse across all of our tests. A proper place to define them is in the common directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
├── cypress │ ├── fixtures │ ├── integration │ │ ├── common │ │ │ └── Navigation.js │ │ ├── Search │ │ │ └── Search.js │ │ ├── Search.feature │ ├── plugins │ │ └── index.js │ └── support │ ├── commands.js │ └── index.js |
cypress/integration/common/Navigation.js
1 2 3 4 5 |
import { Given } from 'cypress-cucumber-preprocessor/steps'; Given('I am on the homepage', () => { cy.visit(''); }); |
Hooks
Aside from defining the background, we can also use Cucumber Before and After hooks.
cypress/integration/Search/Search.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Before, When, And, Then } from 'cypress-cucumber-preprocessor/steps'; Before(() => { cy.visit(''); }); When('I fill the search input with the {string} term', (term) => { cy.get('.search-field').type(term); }); And('I click on the submit button', () => { cy.get('.search-submit').click(); }); Then('I should be redirected to a search page with the results of the {string} search', (term) => { cy.get('.content-box .post-title--archive span').should('contain.text', term); }); |
Our hooks run before and after each of the defined scenarios.
We can also use tagged hooks. They run conditionally if we prepend a scenario with the desired tag.
1 2 3 4 5 |
import { Before } from 'cypress-cucumber-preprocessor/steps'; Before(({ tags: '@homepage' }) => { cy.visit(''); }); |
1 2 3 4 5 6 7 8 |
Feature: Search functionality This feature allows searching through articles using a string @homepage Scenario: Using the search input When I fill the search input with the "JavaScript" term And I click on the submit button Then I should be redirected to a search page with the results of the "JavaScript" search |
Summary
In this article, we’ve gone through both the Gherkin syntax and how to make the Cypress Cucumber integration. It proves to build a bridge between the Quality Assurance team, developers, and less tech-oriented parts of the team. Thanks to approaching testing in such a way, we can keep them well organized and readable.