Using Cucumber.js with Jest - javascript

I am using Jest for my unit tests and I'm in the process of integrating Cucumber.js for running specs written in Gherkin.
I have it all set up and it's working, but I am running into one problem: How can I use Jest's expect? I could use chai's, but I'd like to keep the expect syntax the same between my unit tests and my step definitions (I don't want to.equal in my step definitions and toEqual in my unit tests).
How can I do that? After some digging it seems as if Jest relies on the expect npm package. I could depend on that package explicitly in my package.json, but I'd much rather use my existing Jest dependency. Maybe that's not possible, but I hope it is.
Another option would be to somehow execute the Gherkin specs with the Jest test-runner. I'd be open to that option as well. At the moment I'm running them by calling cucumber.js separately from my Jest test-runner.

My react-native environment:
"cucumber": "^4.1.0",
"jest": "22.4.2",
In my steps definition file, I just require it like this
const { Given, Then, When } = require('cucumber');
const expect = require('expect');
Expect is part of Jest, so you can import it as its own object. Then I can use it wherever I need an assertion. Note: newMember is declared and populated elsewhere.
Given('Sara has provided account details', function() {
for (const prop in newMember) {
expect(newMember[prop]).toBeTruthy();
}
});
Hope that helps.

expect is a globally scoped during jest runtime. So as long as you are running jest it will be available. I'm using this package (needs some config to transform correctly to your babel config): gherkin-jest
Here's a feature using the DOM-testing example from the jest docs:
Feature: Using feature files in jest and cucumber
As a developer
I want to write tests in cucumber and jest
So that businesspeople understand tests and I can test React
Scenario: Emoji toggles upon checking and unchecking the checkbox
Given I did not check the checkbox, so the label is "😭"
When I check the box and the emoji toggles to be "😎"
import {cucumber as c} from 'gherkin-jest'
import React from 'react'
import {mount} from 'enzyme'
import {Checkbox} from '../src/components'
c.defineCreateWorld(() => ({
checkbox:null
}))
c.defineRule('I did not check the checkbox so the label is {string}', (world, off) => {
world.checkbox = mount(<Checkbox labelOff={off} />)
expect(world.checkbox.text()).toBe(off)
})
c.defineRule('I checked the box and the emoji toggles to be {string}', (world, on) =>{
world.checkbox = mount(<Checkbox labelOn={on}/>)
world.checkbox.find('TouchableOpacity').props().onPress()
expect(world.checkbox.text()).toBe(on)
})
This issue I posted gives an example of the config.

An alternative would be to use jest-cucumber
https://www.npmjs.com/package/jest-cucumber.
gives you the flexibility of using both frameworks

Related

Consider using the "jsdom" test environment

I have this simple test:
import React from 'react'
import { render } from '#testing-library/react'
import Button from '.'
describe('Button', () => {
it('renders button without crashing', () => {
const label = 'test'
render(<Button label={label} />)
})
})
And I have a jest.config.json with this content
{
"setupFilesAfterEnv": [
"<rootDir>/lib/settings/setupTests.ts"
]
}
And on my setupTests.ts I have
import '#testing-library/jest-dom'
When I run npm run test (which just run jest), I got the following error:
The error below may be caused by using the wrong test environment, see
https://jestjs.io/docs/configuration#testenvironment-string.
Consider using the "jsdom" test environment.
What I am doing wrong? This used to work before an upgrade.
In your package.json, or jest.config.js/jest.config.ts file, change the value of the testEnvironment property to jsdom.
package.json
"jest":{
"testEnvironment": "jsdom"
}
jest.config.[js|ts]
module.exports = {
"testEnvironment": "jsdom"
}
Important note for jest >28
If you are using jest 28, you will need to install jest-environment-jsdom separately by either:
npm: npm i jest-environment-jsdom --save-dev
yarn: yarn add -D jest-environment-jsdom
Why?
By default, jest uses the node testEnvironment. This essentially makes any tests meant for a browser environment invalid.
jsdom is an implementation of a browser environment, which supports these types of UI tests.
For Jest version 28 and greater, jest-environment-jsdom was removed from the default jest installation to reduce package size.
Additional reading
jest testEnvironment documentation
Jest 28 breaking changes
This can be solved on a per-test-file basis by adding a #jest-environment docblock to the beginning of your file. For example:
/** #jest-environment jsdom */
import React from 'react'
import { render } from '#testing-library/react'
import Button from '.'
describe('Button', () => {
it('renders button without crashing', () => {
const label = 'test'
render(<Button label={label} />)
})
})
If your project has a mix of UI and non-UI files, this is often preferable to changing the entire project by setting "testEnvironment": "jsdom" within your package.json or Jest config. By skipping initializing the JSDom environment for non-UI tests, Jest can run your tests faster. In fact, that's why Jest changed the default test environment in Jest 27.
by default the value for testEnvironment is node which runs all test cases in node.js envioronment, but js-dom provides browser like enviornment. instead of adding jsdom value, you can even add file specific value like below which will work.
/**
* #jest-environment jsdom
*/
// the above comment helps
test('use jsdom in this test file', () => {
const element = document.createElement('div');
expect(element).not.toBeNull();
});
We can even add test file specific environments, please refer this link.
https://jestjs.io/docs/configuration#testenvironment-string
Try this.
module.exports = {
testEnvironment: 'jsdom',
}

How to create Jest Custom Environment with Typescript?

I am trying to create an extension off jest-node-environment as a CustomTestEnvironment but am getting the following error when trying to run jest
● Test suite failed to run
~/git/my-application/tests/environment/custom-test-environment.ts:1
import NodeEnvironment from 'jest-environment-node';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at runTestInternal (../node_modules/jest-runner/build/runTest.js:226:5)
I believe this error means it doesn't recognize this as a typescript file, and hasn't transpiled it. ( I am using the latest version of jest 26.0.1)
Based on discussions in the jest github, the PR to make this work was slated for Jest 26 but was pulled from Jest 26 and is (hopefully) going to be in Jest 27.
https://github.com/facebook/jest/pull/8751
That being said, I've seen samples of people doing it this way online but for me I'm not having any luck following their lead.
import NodeEnvironment from "jest-environment-node";
import {LocalObject} from "./object/local-object.helper";
export class CustomTestEnvironment extends NodeEnvironment {
public async setup(): Promise<void> {
await super.setup();
this.global.localObject = LocalObject.init()
}
public async teardown(): Promise<void> {
LocalObject.teardown(this.global.localObject)
await super.teardown();
}
}
The LocalObject is just a thin wrapper around a test utility which has a complex startup and teardown and which I want to provide to tests to be able to publish test data and kick off the component test.
If I change the imports to be require -
const NodeEnvironment = require("jest-environment-node");
const {LocalObject} = require("./object/local-object.helper");
Then I get this error instead -
SyntaxError: Unexpected token 'export'
If I move the export into an module.exports then I get the following error
public async setup(): Promise<void> {
^^^^^
SyntaxError: Unexpected token 'async'
Seems to me like it's not treating this file as typescript.
Is there any workaround to be able to use this as a typescript file? The LocalObject is written in typescript, so I believe that I need this to be typescript to work right with that one, and it is important that LocalObject file remains typescript for test files to use it correctly.
Alternative question:
Can I do the same kind of setup/teardown logic in the setupFilesAfterEnv I only saw that they were run before, but not after tests. Thanks.
UPDATE: Jest 27 is now released and now supports this. If you are using an older version of Jest, update to Jest 27 to be able to use CustomEnvironment as typescript https://jestjs.io/blog/2021/05/25/jest-27#features-coming-with-breaking-changes
Modules used in the following configuration options are now transformed like the rest of your code, which may be breaking if you relied on them being loaded as-is:
testEnvironment
runner
testRunner
snapshotResolver
Alternatively, you can continue to use the workaround below for prior versions.
This isn't supported in Jest 26 and is slated to be in Jest 27 https://github.com/facebook/jest/pull/8751#issuecomment-699049851
For now the solution is to use setupFilesAfterEnv files in my jest.config https://jestjs.io/docs/en/configuration#setupfilesafterenv-array.
From there I could put all my my setup/teardown in beforeAll() and afterAll() blocks. This was effectively equivalent to using the Node Environment and the setupFilesAfterEnv files is compatible with typescript.
//jest.setup.ts
import {LocalObject} from "./object/local-object.helper";
let localObject: LocalObject;
beforeAll(async () => {
//Start my environment or seed data to DB or whatever
localObject = await LocalObject.init()
}
afterAll(async () => {
//teardown or clean things started in setup my environment
await localObject.teardown()
}

Jest mocking module implementation with .mockImplemetation

Can anyone help here. I am really frustrated with how the mockImplementation works.
So, first of all I am using jest for node testing. I am using the commonjs modules. what I wanna do is that I am trying to mock a module using mockImplementation() and change its implementation between different tests according to this documentation here: https://jestjs.io/docs/en/es6-class-mocks#replacing-the-mock-using-mockimplementation-docs-en-mock-function-api-mockfnmockimplementationfn-or-mockimplementationonce-docs-en-mock-function-api-mockfnmockimplementationoncefn.
My code look like this:
const exportBigQueryTableModule =require('../repository/exportBigQueryTable')
jest.mock('../repository/exportBigQueryTable')
describe('deleting files on table export fail', () => {
mockExportBigQueryTable = jest
.fn(() => Promise.resolve())
.mockResolvedValueOnce()
.mockRejectedValueOnce(new Error('Could not export table'))
exportBigQueryTableModule.mockImplementation(mockExportBigQueryTable)
it(' should do some test', () => {})
})
The problem here is that looks like that this line jest.mock('../repository/exportBigQueryTable') create for me a default mock kind of jest.fn() and the module is always loaded with that default function. So the mock function that I did provide on the test using the mockImplementation never overrides the previous one, I do not get what is the problem here. Why the same exmaple on the official documentation works the only difference is that it uses es6 modules on the doc sample.
I am not sure if I am missing something here.

How to determine if JEST is running the code or not?

I am creating a JS test on my react-native project. I'm specifically using firebase for react native, in which I would like to replace firebase instance with a mockfirebase instance if JS is running the code of my class.
For example I have class setup like below.
import firebase from 'react-native-firebase';
class Database() {
/// use the firebase instance
}
I'd like to have a check if jest is the running environment then I'd replace the import line with appropriate mock class.
jest sets an environment variable called JEST_WORKER_ID so you check if this is set:
function areWeTestingWithJest() {
return process.env.JEST_WORKER_ID !== undefined;
}
I also see that if NODE_ENV is not set the jest CLI sets it to the value 'test'. This might be another way to check.
I usually have NODE_ENV=development set globally on my shell. This works for me:
typeof jest !== 'undefined'
(note that global.jest and 'jest' in global don't work, as this doesn't seem to be a global variable, just a value made available on all modules much like node's require or __filename)
you could add parameter to global for example global.isJest and check on the front end if it is defined
For me best way is checking two things - 0 and undefined:
[0, undefined].includes(process.env.JEST_WORKER_ID)
so it's based on https://stackoverflow.com/a/52231746/3012785

What does #describe and #it methods do in TDD react testing?

I'm learning TDD with React from this site, but don't understand how the author got describe and it, aren't these usually from Jasmine? I don't see this package in the author's node_modules at his github nor does his tests.js import anything that looks like describe or it. Where are these two methods coming from?
import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
describe('Test suite for User component', () => {
it('UserComponent should exist', () => {
let wrapper = shallow(<User />)
expect(wrapper).to.exist;
});
});
If that is the only it() statement that you are going to be using in your test suite, you do not really need the describe() block.
The describe() construct does indeed exist in Jest, it exists in Mocha as well.
The describe() function is used to group together certain sets of tests that have some common setup and tear down for each of them. This is why I said, based on the code you pasted, if you have nothing further to test, you do not need that describe() function.
So when you run create-react-app one of the libraries that got loaded automatically was the Jest test suite.
I would also rewrite that test, instead of UserComponent should exist, I would do
it('shows a user component', () => {
let wrapper = shallow(<User />);
expect(wrapper.find(User).length).toEqual(1);
});
So this is where I recommend to not just follow tutorials, but look up the documentation of the tools they are having you utilize in these tutorials. In the case of Enzyme, there is a really nice method called find() to find the component and it offers an array so you can add on .length and since its just one component of User you can add at the end .toEqual(1);

Categories