Skip to content

a jest transformer for executing cucumber tests in jest

Notifications You must be signed in to change notification settings

mainfraame/cucumber-jest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cucumber-jest

Build Passing Build Passing Branches Functions Lines Statements

A transformer for executing cucumber tests in jest

npm i cucumber-jest -D

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 use a reporter like jest-html-reporters and its attach utility instead
âś… 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.
âś… expressions
âś… Given
setDefaultTimeout use jest.setTimeout or set the timeout property in your jest config
âś… setDefinitionFunctionWrapper
âś… setWorldConstructor
âś… Tags
âś… Then
âś… When

Getting Started

Jest Config

You'll need to add the following to your jest config:

{
    "moduleFileExtensions": [
        "feature",
        // <--- *1
        "js",
        "json",
        "ts",
        "tsx"
    ],
    "setupFilesAfterEnv": [
        "<rootDir>/node_modules/cucumber-jest/dist/init.js",
        // <--- *2
        "<rootDir>/path/to/your/world.ts",
        "<rootDir>/path/to/your/hooks.tsx",
        "<rootDir>/path/to/your/steps.ts"
    ],
    "transform": {
        "^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
        "^.+\\.(feature)$": "cucumber-jest"
        // <--- *3
    },
    "testMatch": [
        "<rootDir>/path/to/your/*.feature"
        // <--- *4
    ]
}
  1. Add feature to moduleFileExtensions
  2. Add "<rootDir>/node_modules/cucumber-jest/dist/init.js" to setupFilesAfterEnv
    • this file calls cucumber's supportCodeLibraryBuilder.reset which sets up cucumber to start capturing registered hooks / steps
    • it's important to note that this file must be list first in the setupFilesAfterEnv list of files; before your world, hooks, or step files
  3. Add "^.+\\.(feature)$": "cucumber-jest" as a transformer
  4. Add "<rootDir>/path/to/your/*.feature" as a testMatch pattern

Example

The code-snippets below are taken from the example project

World

setWorldConstructor allows you to set the context of "this" for your steps/hooks definitions.

This can be helpful when you want to maintain state, access globals, or assign component testing classes. The values are accessible within all Hooks and Steps.

import { setWorldConstructor } from '@cucumber/cucumber';

import Element from './element';
import { $server, $spy } from './mocks';

/**
 *  Element class is a helper provided in the example project
 *  for more details, please see the example project
 */

export class TestWorld {
    $server = $server; // reference to mws (mock-server-worker) setupServer
    $spy = $spy;       // jest.fn()

    email = new Element({name: 'email'});
    extraEmails = new Element({name: 'extraEmails'});
    firstName = new Element({name: 'firstName'});
    lastName = new Element({name: 'lastName'});
    password = new Element({name: 'password'});
    reset = new Element({dataId: 'reset'});
    submit = new Element({dataId: 'submit'});
    successAlert = new Element({dataId: 'successAlert'});
    showExtraEmailsAlert = new Element({dataId: 'showExtraEmailsAlert'});
}

setWorldConstructor(
    TestWorld
);

Hooks

*Unlike cucumber.js, "this" is accessible inside BeforeAll and AfterAll hooks, and they receive two additional parameters: Spec and the fileName

If you are using these two params with typescript, you'll notice that it shows a typing error. The library does not currently augment cucumber's hook typings to add the two additional params; if anyone has a solution, please contribute.

import { After, AfterAll, Before, BeforeAll } from '@cucumber/cucumber';
import { Spec } from 'cucumber-jest';
import { advanceTo, clear } from 'jest-date-mock';
import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';

import { SignUp } from '../src/signUp';
import { TestWorld } from './world';

const $root = document.createElement('div');

document.body.appendChild($root);

BeforeAll(async function (this: TestWorld, spec: Spec, fileName: string) {
    console.log('spec::', spec);
    console.log('fileName::', fileName);

    this.$server.listen();

    advanceTo(new Date('2019-12-01T15:00:00.000Z'));

    act(() => {
        ReactDOM.render(<SignUp / >, $root);
    });
});

Before(function (this: TestWorld) {
    // do something before each spec
});

After(function (this: TestWorld) {
    this.$spy.mockClear();
    this.$server.resetHandlers();
});

AfterAll(async function (this: TestWorld) {
    clear();

    act(() => {
        ReactDOM.unmountComponentAtNode($root);
    });

    this.$server.close();
});

Steps

import { Given, Then, When } from '@cucumber/cucumber';

import type { TestWorld } from './world';

Given(/^the (\S+) component rendered$/,
    async function (this: TestWorld, name) {
        await this[name].click();
    }
);

When('the {word} button is clicked',
    async function (this: TestWorld, name) {
        await this[name].click();

        await this[name].waitForEnabled();
    }
);

When(
    /^the (\S+) text input value is (.*)$/,
    async function (this: TestWorld, name, value) {
        await this[name].setValue(value);
    }
);

When(
    /^the (\S+) checkbox input is (checked|not checked)$/,
    async function (this: TestWorld, name, state) {
        const currentValue = this[name].getAttribute('checked');

        if (currentValue !== (state === 'checked')) {
            await this[name].click();
        }
    }
);

Then(
    /^(GET|PUT|POST|DELETE) (.*) is called with the (request body|params):$/,
    function (this: TestWorld, method, url, type, value) {
        const hasBody = type === 'request body';

        expect(this.$spy).toHaveBeenCalledWith({
            url,
            method,
            ...(hasBody ? {data: value} : {params: value})
        });
    }
);

Then(
    /^the (\S+) is (visible|not visible)$/,
    async function (this: TestWorld, name, state) {
        await this[name][
            state === 'visible' ? 'waitForInDom' : 'waitForNotInDom'
            ]();

        expect(this[name].isInDom()).toEqual(state === 'visible');
    }
);

Then(
    /^the (\S+) inner text is "(.*)"$/,
    function (this: TestWorld, name, innerText) {
        expect(this[name].innerText()).toEqual(innerText);
    }
);

Feature

Feature: Sign Up

  Scenario: Without Extra Emails
    Given the firstName text input value is James
    And the lastName text input value is Dean
    And the email text input value is james.dean@gmail.com
    And the password text input value is itsASecretShh...
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": false,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is not visible

  Scenario: With Extra Emails
    Given the firstName text input value is James
    And the lastName text input value is Dean
    And the email text input value is james.dean@gmail.com
    And the password text input value is itsASecretShh...
    And the extraEmails checkbox input is checked
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": true,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is visible

Output

Below is an example output from running tests against the example project

  PASS  test/features/scenario.feature (110 MB heap size)
  Feature: Sign Up
    Scenario: Without Extra Emails
      âś“ Given the firstName text input value is James (21 ms)
      âś“ And the lastName text input value is Dean (8 ms)
      âś“ And the email text input value is [email protected] (8 ms)
      âś“ And the password text input value is itsASecretShh... (10 ms)
      âś“ When the submit button is clicked (200 ms)
      âś“ Then POST /api/sign-up is called with the request body:
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": false,
           "date": "2019-12-01T15:00:00.000Z"
       } (2 ms)
      âś“ And the successAlert is visible (26 ms)
      âś“ And the showExtraEmailsAlert is not visible (12 ms)
    Scenario: With Extra Emails
      âś“ Given the firstName text input value is James (16 ms)
      âś“ And the lastName text input value is Dean (9 ms)
      âś“ And the email text input value is [email protected] (11 ms)
      âś“ And the password text input value is itsASecretShh... (10 ms)
      âś“ And the extraEmails checkbox input is checked (45 ms)
      âś“ When the submit button is clicked (84 ms)
      âś“ Then POST /api/sign-up is called with the request body:
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": true,
           "date": "2019-12-01T15:00:00.000Z"
       } (1 ms)
      âś“ And the successAlert is visible (2 ms)
      âś“ And the showExtraEmailsAlert is visible (1 ms)

Test Suites: 1 passed, 1 total
Tests:       17 passed, 17 total
Snapshots:   0 total
Time:        5.066 s

Tags [Experimental]

Built-In

There are two tags that come built in:

tag description
@skip skips the scenario
@debug only execute that scenario in the feature file

@debug Example

Feature: Sign Up W/ Background & Tags Example

  Background:
    Given the firstName text input value is James
    And the lastName text input value is Dean
    And the email text input value is james.dean@gmail.com
    And the password text input value is itsASecretShh...

  @debug
  Scenario: Without Extra Emails
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": false,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is not visible

  Scenario: With Extra Emails
    When the extraEmails checkbox input is checked
    And the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": true,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is visible

@skip Example

Feature: Sign Up W/ Background & Tags Example

  Background:
    Given the firstName text input value is James
    And the lastName text input value is Dean
    And the email text input value is james.dean@gmail.com
    And the password text input value is itsASecretShh...

  @skip
  Scenario: Without Extra Emails
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": false,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is not visible

  Scenario: With Extra Emails
    When the extraEmails checkbox input is checked
    And the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "James",
           "lastName": "Dean",
           "email": "[email protected]",
           "password": "itsASecretShh...",
           "extraEmails": true,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is visible
    And the showExtraEmailsAlert is visible

Custom

Tags are supported by using the environment variable TAGS with a comma delimited list of strings. For best results, use the tags only at the scenario level. Support for feature level will be added later. Unfortunately, jest cli does not support custom commands and will throw an error if you try to use them. For this reason, we need to use environment variables to pass these.

Inclusive Example:

TAGS=foo,bar jest

TAGS=foo, bar jest # space is trimmed

Exclusive Example:

TAGS="not foo" jest

TAGS="not foo, not bar" jest

Exclusive & Inclusive Example:

TAGS="bar, not foo" jest

Variables

You can inject values into your feature files using variables.

When variable files are found and injected into a feature file has variables injected, a temporary feature file with the same name is written to your os's temporary folder; eg. for mac users, /var/folders/sb/2wr3vgcd1sxg4tgv1mr8dtq80000gn/T/cucumber-jest

If you need to figure out the temporary folder path, run the jest with this environment variable CUCUMBER_JEST_SHOW_TEMP_PATH=true.

Use Without ENV

Set up a file where the name matches the file you're targeting and include .vars in the name, eg.

Feature File Variable File
scenarioOutline.feature scenarioOutline.vars.js
scenarioOutline.feature scenarioOutline.vars.json
scenarioOutline.feature scenarioOutline.vars.ts

Use With ENV

Alternatively, you can have variables target an environment by setting the ENV environment variable and using it in the variables file name.

Below are multiple examples with different ENV values and extensions:

Environment Variable Feature File Variable File
ENV=dev scenarioOutline.feature scenarioOutline.vars.dev.js
ENV=dev scenarioOutline.feature scenarioOutline.vars.dev.json
ENV=dev scenarioOutline.feature scenarioOutline.vars.dev.ts
ENV=qa scenarioOutline.feature scenarioOutline.vars.qa.js
ENV=qa scenarioOutline.feature scenarioOutline.vars.qa.json
ENV=qa scenarioOutline.feature scenarioOutline.vars.qa.ts

Example Variable Use

Properties in your variable files can be used in your feature file by prefixing the json path with $, eg.

Property Name In Feature File Property Name In Variable File
$firstName firstName

** nested structures are also supported, please see examples below.

Example With ENV & Flat Variables Structure

Testing Command:

ENV=dev $(npm bin)/jest

Variables File: scenarioOutline.vars.dev.ts

export default {
    email: '[email protected]',
    firstName: 'James',
    lastName: 'Dean',
    password: 'itsASecretShh...'
};

Feature File: scenarioOutline.feature

Feature: Sign Up

  Scenario Outline: Submitting <prefix> Extra Emails
    Given the firstName text input value is $firstName
    And the lastName text input value is $lastName
    And the email text input value is $email
    And the password text input value is $password
    And the extraEmails checkbox input is <extraEmails>
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "$firstName",
           "lastName": "$lastName",
           "email": "$email",
           "password": "$password",
           "extraEmails": <extraEmailsValue>,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is <successAlert>
    And the showExtraEmailsAlert is <showExtraEmailsAlert>

    Examples:
      | prefix  | extraEmails | extraEmailsValue | successAlert | showExtraEmailsAlert |
      | With    | not checked | false            | visible      | not visible          |
      | Without | checked     | true             | visible      | visible              |

Example With ENV & Nested Variables Structure

Testing Command:

ENV=qa $(npm bin)/jest

Variables File: scenarioOutline.vars.qa.ts

export default {
    user: {
        email: '[email protected]',
        firstName: 'James',
        lastName: 'Dean',
        password: 'itsASecretShh...'
    }
};

Feature File: scenarioOutline.feature

Feature: Sign Up - Scenario Outline [Nested]

  Scenario Outline: Submitting <prefix> Extra Emails
    Given the firstName text input value is $user.firstName
    And the lastName text input value is $user.lastName
    And the email text input value is $user.email
    And the password text input value is $user.password
    And the extraEmails checkbox input is <extraEmails>
    When the submit button is clicked
    Then POST /api/sign-up is called with the request body:
      """
       {
           "firstName": "$user.firstName",
           "lastName": "$user.lastName",
           "email": "$user.email",
           "password": "$user.password",
           "extraEmails": <extraEmailsValue>,
           "date": "2019-12-01T15:00:00.000Z"
       }
      """
    And the successAlert is <successAlert>
    And the showExtraEmailsAlert is <showExtraEmailsAlert>

    Examples:
      | prefix  | extraEmails | extraEmailsValue | successAlert | showExtraEmailsAlert |
      | With    | not checked | false            | visible      | not visible          |
      | Without | checked     | true             | visible      | visible              |

** to accessing properties, please use standard javascript property pathing.

Examples

Type Feature File Variable File
Global Variables Without Env scenarioOutlineNestedGlobal global.vars.ts
Variables Without Env scenarioOutline scenarioOutline.vars.ts
Global Variables With Env scenarioOutlineNestedGlobalEnv global.vars.dev.ts
Variables With (dev) scenarioOutlineNested scenarioOutlineNested.vars.dev.ts

Variable File Rules

  • must be located within your project
  • uses an extension defined in your jest configuration: moduleFileExtensions
  • can be parsed into a javascript object, eg. .js, .json, .ts
  • global files, as the name implies, can be used in any feature file
  • when providing an env, it will prioritize files with an env over those that do not. eg, ENV=dev = feature.vars.dev > feature.vars
  • global and feature specific variable files will get merged together into a single object.
  • when global and feature specific variable files have the same variable paths, the feature specific values will be prioritized.

Cucumber Docs

Here are some useful links to the cucumber-js docs: