npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

jest-runner-cucumber

v0.0.21

Published

a jest runner for writing cucumber tests

Downloads

6

Readme

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 | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | :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:

  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 [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:

  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": "[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 () {
    }
}));