jest-runner-cucumber
v0.0.21
Published
a jest runner for writing cucumber tests
Downloads
6
Maintainers
Readme
jest-runner-cucumber
Jest Test Runner for the Cucumber Framework
npm i jest-runner-cucumber
Table of Contents
Gherkin Features
| Supported | Feature | Notes | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | :white_check_mark: | And | | | :white_check_mark: | Background | | | :white_check_mark: | But | | | :white_check_mark: | Comments | | | :white_check_mark: | Data Table | | | :white_check_mark: | DocString | if it finds the docString is JSON, it will parse it for you | | | Rule | haven't seen examples of this; not sure if it's worth it | | :white_check_mark: | Scenario | | | :white_check_mark: | Scenario Outline | |
Cucumber Features
| Supported | Feature | Notes | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | :white_check_mark: | After | called after each scenario in a feature file | | :white_check_mark: | AfterAll | called after the feature file is completed; unlike Cucumber, you will have access to "this" context here. | | | Attachments | | | :white_check_mark: | Before | called before each scenario per feature file | | :white_check_mark: | BeforeAll | called before the feature file is started; unlike Cucumber, you will have access to "this" context here. | | :white_check_mark: | Given | | | | setDefaultTimeout | use jest.setTimeout or set the timeout property in your jest config | | :white_check_mark: | setDefinitionFunctionWrapper | | | :white_check_mark: | setWorldConstructor | | | | Tags | need to identify a way to pass tags through jest | | :white_check_mark: | Then | | | :white_check_mark: | When | |
Additional Features
| Supported | Feature | Notes | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | :white_check_mark: | gherkin variables used to populate feature files | |
Getting Started
Jest Config
If you have existing jest test cases that do not use Cucumber, you have two options:
create a separate configuration. You can use the Jest CLI to run against specific configurations:
jest --config=path/to/your/config.json
add a "projects" array to your existing configuration; moving any existing test configuration to inside of the projects array. Then, add your new jest configuration:
{ "projects": [ { "displayName": "Unit" }, { "displayName": "Integration", "runner": "jest-runner-cucumber" } ] }
moduleFileExtensions:
"moduleFileExtensions": [
"feature",
"js",
"jsx",
"ts",
"tsx"
]
* If you are not using typescript, remove "ts"
and "tsx"
runner:
"runner": "jest-runner-cucumber"
setupFiles (optional):
"setupFiles": [
"<rootDir>/path/to/your/window-polyfill.ts"
]
* Add your polyfills here. Here's an example
setupFilesAfterEnv:
"setupFilesAfterEnv": [
"<rootDir>/path/to/your/world.ts",
"<rootDir>/path/to/your/hooks.tsx",
"<rootDir>/path/to/your/steps.ts"
]
testMatch:
"testMatch": [
"<rootDir>/path/to/your/features/*.feature"
]
transform:
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
}
* If you are not using typescript, remove "ts"
and "tsx"
restoreMocks (optional):
"restoreMocks": true
If you are planning on writing integration tests, I highly recommend that you set this to true. There is an open bug for jest to fix an issue where it does not unset manual mocks that are defined using __mock__ folders. However, if this is set true, jest-runner-cucumber will perform a scan of all __mock__ folders and files and manually unmock them for you.
Cucumber
Feature
path/to/your/features/button.feature
Feature: Button
Given I go to home
When I click the login button
Then the login button is not visible
Hooks
path/to/your/hooks.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils'
import { AfterAll, BeforeAll } from 'cucumber';
import SignUp from './path/to/your/app';
BeforeAll(async function () {
await act(async () => {
ReactDOM.render(
<SignUp/>,
document.body
)
});
});
AfterAll(async function () {
await act(async () => {
ReactDOM.unmountComponentAtNode(
document.body
)
});
});
You can choose to use the hooks to render/unmount your component before/after each feature file like above, or you can add a path to your application entry point to your jest configuration's setupFiles property. The latter is more performant.
Steps
path/to/your/steps.ts
import { Given, When, Then } from 'cucumber';
import { act } from 'react-dom/test-utils';
Given(/I go to (.*)$/, function(link) {
window.location.hash = `#/${link}`;
});
When(/I click the (\S+) button$/, async function(name) {
await act(async () => {
document.querySelector(`[data-test-id="${name}"]`).click();
});
});
Then(/the (\S+) button is (visible|not visible)$/, function(name, state) {
expect(!!document.querySelector(`[data-test-id="${name}"]`))
.toEqual(state === 'visible')
});
World
setWorldConstuctor allows you to set the context of "this" for your steps/hooks definitions. This can be helpful when you want to maintain state between steps/hooks or want your steps/hooks to have access to some predefined data. The values are accessible within all Hooks, and Steps by using this
path/to/your/world.ts
import { setWorldConstructor } from 'cucumber';
setWorldConstructor(
class MyWorld {
pages = [];
}
);
Example Output
Below is an example output from running tests against the example
PASS test/features/scenarioOutline.feature (97 MB heap size)
Feature: Sign Up - Submitting With Extra Emails
✓ Given the firstName text input value is Dayne (37 ms)
✓ And the lastName text input value is Mentier (11 ms)
✓ And the email text input value is [email protected] (13 ms)
✓ And the password text input value is itsASecretShh... (9 ms)
✓ And the extraEmails checkbox input is not checked (2 ms)
✓ When the submit button is clicked (89 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body: (3 ms)
✓ And the successAlert is visible (2 ms)
✓ And the showExtraEmailsAlert is not visible (2 ms)
Feature: Sign Up - Submitting Without Extra Emails
✓ Given the firstName text input value is Dayne (12 ms)
✓ And the lastName text input value is Mentier (11 ms)
✓ And the email text input value is [email protected] (8 ms)
✓ And the password text input value is itsASecretShh... (10 ms)
✓ And the extraEmails checkbox input is checked (9 ms)
✓ When the submit button is clicked (45 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body: (1 ms)
✓ And the successAlert is visible (1 ms)
✓ And the showExtraEmailsAlert is visible (1 ms)
PASS test/features/scenario.feature (93 MB heap size)
Feature: Sign Up - Without Extra Emails
✓ Given the firstName text input value is Dayne (11 ms)
✓ And the lastName text input value is Mentier (12 ms)
✓ And the email text input value is [email protected] (11 ms)
✓ And the password text input value is itsASecretShh... (14 ms)
✓ When the submit button is clicked (66 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body: (5 ms)
✓ And the successAlert is visible (2 ms)
✓ And the showExtraEmailsAlert is not visible (2 ms)
Feature: Sign Up - With Extra Emails
✓ Given the firstName text input value is Dayne (14 ms)
✓ And the lastName text input value is Mentier (12 ms)
✓ And the email text input value is [email protected] (12 ms)
✓ And the password text input value is itsASecretShh... (9 ms)
✓ And the extraEmails checkbox input is checked (9 ms)
✓ When the submit button is clicked (49 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body: (1 ms)
✓ And the successAlert is visible (2 ms)
✓ And the showExtraEmailsAlert is visible (1 ms)
PASS test/features/scenarioBackground.feature (85 MB heap size)
Feature: Sign Up - Without Extra Emails
✓ Given the firstName text input value is Dayne (14 ms)
✓ And the lastName text input value is Mentier (13 ms)
✓ And the email text input value is [email protected] (15 ms)
✓ And the password text input value is itsASecretShh... (22 ms)
✓ When the submit button is clicked (66 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body: (3 ms)
✓ And the successAlert is visible (4 ms)
✓ And the showExtraEmailsAlert is not visible (2 ms)
Feature: Sign Up - With Extra Emails
✓ Given the firstName text input value is Dayne (10 ms)
✓ And the lastName text input value is Mentier (8 ms)
✓ And the email text input value is [email protected] (10 ms)
✓ And the password text input value is itsASecretShh... (8 ms)
✓ And the extraEmails checkbox input is checked (7 ms)
✓ When the submit button is clicked (46 ms)
✓ Then POST http://127.0.0.1:8080/api/sign-up is called with the request body:
✓ And the successAlert is visible (2 ms)
✓ And the showExtraEmailsAlert is visible (1 ms)
Test Suites: 3 passed, 3 total
Tests: 52 passed, 52 total
Snapshots: 0 total
Time: 7.603 s
Ran all test suites.
Gherkin Variables
This provides the ability to define variables in your feature files, and hold the values in a separate file. A few things to note for this functionality is:
- the file must contain the same name as the feature file you're looking to populate
- all variables start with a "$"; eg, in the feature file, the variable would be defined as $email, while the vars file would contain email
- you can further split up your vars files by using the CUCUMBER_ENV variable. Using that, your files would look like this:
featureFileName.CUCUMBER_ENV.vars.{js,ts,json}
For an example, see the example scenarioOutline feature file, and the accompanying variable file
MockXHR
One of the hardest thing I've found when using jest as a runner for integration tests is figuring out how to properly spy on api calls and mock the requests. I've included a helper class called MockXHR that simplifies the process. Below is an example of how to set it up in your World and Hooks
import React from 'react';
import ReactDOM from 'react-dom';
import { After, AfterAll, BeforeAll,setWorldConstructor } from 'cucumber';
import { MockXHR } from 'jest-runner-cucumber/dist/mocks/MockXHR';
import TestApp from 'my/root/path';
setWorldConstructor(
class TestWorld {
$mockXhr = new MockXHR([
{
url: '/api/sign-up',
method: 'post',
status: 200,
response: {
message: 'thanks for signing up!'
}
},
{
url: '/api/sign-up',
method: 'get',
status: 200,
response: {
registered: false
}
}
])
}
)
After(function () {
this.$mockServer.spy.mockClear();
});
AfterAll(async function () {
ReactDOM.unmountComponentAtNode(document.body);
this.$mockServer.destroy();
});
BeforeAll(async function () {
ReactDOM.render(
<TestApp/>,
document.body
);
});
MockXHR provides a spy that is called whenever a request goes out, this can be use your steps like this:
import { Then } from 'cucumber'
Then(/^(GET|PUT|POST|DELETE) (.*) is called with the (request body|params):$/,
function (method, url, type, value) {
const hasBody = type === 'request body';
expect(this.$mockServer.spy).toHaveBeenCalledWith({
...hasBody ? {data: value} : {params: value},
method,
url
});
}
);
Scenario: Without Extra Emails
When the submit button is clicked
Then POST /api/sign-up is called with the request body:
"""
{
"firstName": "Dayne",
"lastName": "Mentier",
"email": "[email protected]",
"password": "itsASecretShh...",
"extraEmails": false
}
"""
Internally, it uses xhr-mock. Unlike nock which causes memory leak issues because it is mutating native node modules, xhr-mock does not. I've also found that if your http lib is axios, you can also run into memory leak issues if you do not mock the follow-redirects. That lib has the same issue as nock; it mutates the native http and https modules, which causes leaking. If you are using axios make sure you add the following mock to one of your entry files:
jest.mock('follow-redirects', () => ({
http: function () {
},
https: function () {
}
}));