jest-runner-cucumber
TypeScript icon, indicating that this package has built-in type declarations

0.0.21 • Public • Published

jest-runner-cucumber

Build Passing Build Passing

Jest Test Runner for the Cucumber Framework

npm i jest-runner-cucumber

Table of Contents

Gherkin Features

Supported Feature Notes
And
Background
But
Comments
Data Table
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
Scenario
Scenario Outline

Cucumber Features

Supported Feature Notes
After called after each scenario in a feature file
AfterAll called after the feature file is completed; unlike Cucumber, you will have access to "this" context here.
Attachments
Before called before each scenario per feature file
BeforeAll called before the feature file is started; unlike Cucumber, you will have access to "this" context here.
Given
setDefaultTimeout use jest.setTimeout or set the timeout property in your jest config
setDefinitionFunctionWrapper
setWorldConstructor
Tags need to identify a way to pass tags through jest
Then
When

Additional Features

Supported Feature Notes
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:

  1. create a separate configuration. You can use the Jest CLI to run against specific configurations: jest --config=path/to/your/config.json

  2. 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 dayne.mentier@gmail.com (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 dayne.mentier@gmail.com (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 dayne.mentier@gmail.com (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 dayne.mentier@gmail.com (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 dayne.mentier@gmail.com (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 dayne.mentier@gmail.com (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:

  1. the file must contain the same name as the feature file you're looking to populate
  2. all variables start with a "$"; eg, in the feature file, the variable would be defined as $email, while the vars file would contain email
  3. 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": "dayne.mentier@gmail.com",
       "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 () {
    }
}));

Package Sidebar

Install

npm i jest-runner-cucumber

Weekly Downloads

12

Version

0.0.21

License

MIT

Unpacked Size

236 kB

Total Files

83

Last publish

Collaborators

  • dayne.mentier