Consider using the "jsdom" test environment - javascript

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',
}

Related

How can I pre-render a react app in gulp/node?

How can I programmatically render a react app in gulp and node 12?
I taking over and upgrading an old react (0.12.0) app to latest. This also involved upgrading to ES6. The react code itself is done, but we also need to prerender the application (The app is an interactive documentation and must be crawled by search engines).
Previously, the gulp build process ran browserify on the code and then ran it with vm.runInContext:
// source code for the bundle
const component = path.resolve(SRC_DIR + subDir, relComponent);
vm.runInNewContext(
fs.readFileSync(BUILD_DIR + 'bundle.js') + // ugly
'\nrequire("react").renderToString(' +
'require("react").createElement(require(component)))',
{
global: {
React: React,
Immutable: Immutable,
},
window: {},
component: component,
console: console,
}
);
I am suprised it worked before, but it really did. But now it fails, because the source uses ES6.
I looked for pre-made solutions, but they seem all targeting old react versions, where react-tools was still around.
I packaged the special server-side script below with browserify & babel and then ran it using runInNewContext. It does not fail but also not output any code, it just logs an empty object
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
I found tons of articles about "server-side rendering", but they all seem to be about rendering with express and use the same lines as the script above. I can't run that code directly in gulp, as it does not play well with ES6 imports, which are only available after node 14 (and are experimental).
I failed to show the gulp-browserify task, which was rendering the app component directly, instead of the server-side entrypoint script above. In case anyone ever needs to do this, here is a working solution.
Using vm.runInNewContext allows us to define a synthetic browser context, which require does not. This is important if you access window anywhere in the app.
src/server.js:
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
global.output = content;
above script serves as entry point to browserify. Gulp task to compile:
function gulpJS() {
const sourcePath = path.join(SRC_DIR, 'src/server.js');
return browserify(sourcePath, { debug:true })
.transform('babelify', {
presets: [
["#babel/preset-env", { targets: "> 0.25%, not dead" }],
"#babel/preset-react",
],
})
.bundle()
.pipe(source('server_output.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('.'))
.pipe(dest(BUILD_DIR));
}
The generated file can now be used by later tasks, e.g. to insert the rendered content into a HTML file.
const componentContent = fs.readFileSync(path.join(BUILD_DIR, 'server.js'));
const context = {
global: {
React: React,
Immutable: Immutable,
data: {
Immutable
},
},
window: {
addEventListener() { /* fake */ },
removeEventListener() { /* fake */ },
},
console,
};
vm.runInNewContext(componentContent, context);
const result = context.global.output;

Trying to remove 'react-hot-loader' from final bundle using webpack ignorePlugin

This is what the react-hot-loader DOCs says:
https://www.npmjs.com/package/react-hot-loader
Note: You can safely install react-hot-loader as a regular dependency instead of a dev dependency as it automatically ensures it is not executed in production and the footprint is minimal.
Even though it says that. My goals are:
I want to remove react-hot-loader from my production bundle.
And I also want a single App.js file. That should work for DEV and PROD.
The only command that I have related to react-hot-loader is inside of my App.js file:
App.js
import { hot } from 'react-hot-loader/root';
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
return(
<Layout/>
);
}
export default process.env = hot(App);
If I run it just like this, I end up with the following line on my app.js transpiled and bundled file:
/* WEBPACK VAR INJECTION /(function(process) {/ harmony import / var react_hot_loader_root__WEBPACK_IMPORTED_MODULE_0__ = webpack_require(/! react-hot-loader/root */ "wSuE");
That's expected.
But if I change my App.js file to:
AppV2.js
import { hot } from 'react-hot-loader/root'; // KEEPING THE IMPORT
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
console.log(window);
return(
<Layout/>
);
}
// export default hot(App); <--- COMMENTED OUT THE hot() LINE
export default App;
And I add this line to my webpack.config.js
webpack.config.js
plugins:[
new webpack.IgnorePlugin(/react-hot-loader/)
]
I'll end up with a new transpiled app.js file with this line:
*** !(function webpackMissingModule() { var e = new Error("Cannot find module 'react-hot-loader/root'"); e.code = 'MODULE_NOT_FOUND'; throw e; }());
Note: The first '***' chars in the line above don't really exist. I had to add them in order to the ! exclation mark to be shown in the quote. Don't know why but you can't start a quote with an exclamation mark.
QUESTION
Isn't the IgnorePlugin supposed to completely ignore the react-hot-loader package? Why is it being marked as missing? See that it's not even being used on the code (since I've commented out the hot() call).
Ignore Plugin only excludes that particular module in bundle generation. However it will not remove the references to the module from your source code. Hence your webpack output is throwing that error.
One way of bypassing this error is to use the DefinePlugin to create a dummy stub for react-hot-loader. More on that here.
That said react-hot-loader itself proxies the children without any changes if the NODE_ENV is production. Check here and here. So in production mode apart from the hot() function call which directly returns your component, there is no other stuff that happens.
Another option could be:
// App.js
export default function AppFactory() {
if (process.env.NODE_ENV === "development") {
return hot(App);
} else {
return App;
}
}
// index.js:
import AppFactory from './App';
const App = AppFactory();
// ...
<App />
Now since webpack is creating bundles at build time, it knows if the mode is development or production (more on build modes) and should be able to eliminate the dead code with tree shaking and UglifyjsWebpackPlugin.
Make sure that if you are using Babel it's not transpiling your code to CommonJS - see Conclusion section, point 2 of the tree shaking page.
Pass the ambient mode to babel.
"scripts": {
"build-dev": "webpack --node-env development",
"build-prod": "webpack --node-env production",
},

How can I run a global setup script before any mocha test in watch mode

I want to run mocha tests in a TDD manner (--watch mode), which works fine.
But I have a "global setup.js" file, which mocks part of the application, that is used by most tests.
If I run the tests normally or in watch mode for the first time everything is fine because the setup script loads.
If a test or source file is changed, however, only the relevant tests run (sounds awesome in theory) but since my global mocking script is not run the tests fail.
How can I execute a setup script each time (once per overall test run) even in watch mode with mocha?
This is the command I use:
vue-cli-service test:unit --watch
# pure mocha would be (I assume)
mocha 'tests/**/*.spec.js' --watch
I have tried using the --require and --file option, but they are also not rerun on file changes.
I am using a vue app created with the VUE CLI and this is how my code looks
// setup.spec.js
import { config } from "#vue/test-utils";
before(() => {
config.mocks["$t"] = () => {};
});
// some_test.spec.js
import { expect } from "chai";
import { shallowMount } from "#vue/test-utils";
import MyComp from "#/components/MyComp.vue";
describe("MyComp", () => {
it("renders sth", () => {
const wrapper = shallowMount(MyComp);
expect(wrapper.find(".sth").exists()).to.be.true;
});
});
This isn't a very satisfying answer because it feels like there should be a better way but you can import your setup script into the individual test files.
For example:
// some_test.spec.js
import 'setup.spec.js' //<-- this guy right here
import { expect } from "chai";
import { shallowMount } from "#vue/test-utils";
import MyComp from "#/components/MyComp.vue";
describe("MyComp", () => {
it("renders sth", () => {
const wrapper = shallowMount(MyComp);
expect(wrapper.find(".sth").exists()).to.be.true;
});
});
Feels sub optimal, but it is better than replicating logic everywhere.
Have you tried utilizing .mocharc.js file to setup your mocha configurations before you run a test?
'use strict';
module.exports = {
package: './package.json',
watch: true,
timeout: 100000
};

Configuring Enzyme 3.x Adapter

I am writing tests for a React app using Jest and Enzyme. Enzyme 3.x introduced Adapters to provide compatibility across different versions of React. The installation documentation gives examples how to set this up:
// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
What is this "setup file" referred to in the comment? Is this a file which can be run before all tests? Where does this file go in a React project? Does it need a particular name?
It doesn't need a particular name, and yes, it's run before any tests are.
You hook it up in your package.json's jest stanza.
This is an example from a project I'm working on.
"jest": {
// other stuff...
"setupFiles": [
"./js/jest-setup.js"
],
// ....
}
The actual js/jest-setup.js file looks like this (i.e. like your example).
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});
Enzyme has a nifty guide on setup here: https://github.com/airbnb/enzyme/blob/master/docs/guides/jest.md
Though the initial step doesn't say it is the package.json.
Posting the relevant sections (with some minor tweaks) here so we don't lose it:
Configure with Jest
To run the setup file to configure Enzyme and the Adapter with Jest direct setupTestFrameworkScriptFile to literally the string <rootDir> and the path to your setup file.
package.json:
{
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>src/setupTests.js"
}
}
Jest version 15 and up
Starting with version 15, Jest no longer mocks modules by default. Because of this, you no longer have to add any special configuration for Jest to use it with enzyme.
Install Jest, and its Babel integrations, as recommended in the Jest docs. Install enzyme. Then, simply require/import React, enzyme functions, and your module at the top of a test file.
setupTests.js:
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Foo from '../Foo';
What is this "setup file" referred to in the comment? :
It's a way of running something before your test starting (some config for instance)
If you create your React Project using react-create-app you need or eject your application or pass a command set up your file setupTest.js (The name does not matter), but you need to identify in your command line, like this:
package.json
"scripts": {
"test": "react-scripts test --env=jsdom --setupFiles=./src/test-setup.js",
},
test-setup.js
import './shim';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
shim.js (this is a hack to avoid the warning)
global.requestAnimationFrame = (callback) => {
setTimeout(callback, 0);
};
Jest & enzyme configuration :
Add following code in package.json
"jest": {
"testEnvironment": "jsdom",
"moduleDirectories": [
"src",
"node_modules"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(jpg|gif|ttf|eot|svg)$": "<rootDir>/__mocks__/fileMock.js"
},
"transform": {
"^.+\\.(js|jsx)$": "babel-jest",
".+\\.(css|styl|less|sass|scss)$": "<rootDir>/node_modules/jest-css-modules-transform"
},
"setupTestFrameworkScriptFile": "<rootDir>/setupTests.js",
"setupFiles": [
"<rootDir>setup.js"
],
"moduleFileExtensions": [
"css",
"scss",
"js",
"json",
"jsx"
],
"testRegex": "\/test\/spec\/.*\\.js$",
"transformIgnorePatterns": [
"/node_modules/(?!test-component).+\\.js$"
]
}
For setup of Enzyme => setup.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure({ adapter: new Adapter() })
For setupTestFrameworkScriptFile : setupTests.js
global.fetch = require('jest-fetch-mock')
const { JSDOM } = require('jsdom')
const jsdom = new JSDOM('<!doctype html><html><body></body></html>')
const { window } = jsdom
const exposedProperties = ['window', 'navigator', 'document']
const { document } = new JSDOM('').window
global.document = document
global.window = document.defaultView
global.HTMLElement = window.HTMLElement
global.HTMLAnchorElement = window.HTMLAnchorElement
Object.keys(document.defaultView).forEach(property => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property)
global[property] = document.defaultView[property]
}
})
global.navigator = {
userAgent: 'node.js',
}
I was just having this same issue and the easiest solution was to simply create a file named exactly setupTests.js, in the src/ directory with the contents:
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
Jest automatically detects it and the error goes away.
With enzyme 3 we need an adapter. Adapter allows us to specify exactly which version of react we are gonna test against. This allows core enzyme library to be smaller. It does not need to have all of the code for all of the various versions of react that supported. Instead you just specify which one you need by installing the adapter that keeps the core library light and keeps your entire code base more manageable.
this is how u configure it:
in tests directory create any .js file:
tests/setupTest.js
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
in the root directory create jest.config.json file and put this
{
"setupFiles": ["raf/polyfill", "<rootDir>/src/tests/setupTest.js"],
}
NOTE: We also need to install a polyfill module. Polyfill is known as request animation frame. Since we do not have it in test environment, we need to install.
npm i raf --save
finally in package.json :
"test": "jest --config=jest.config.json"

Using Cucumber.js with Jest

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

Categories