This tutorial will explain how to set up jasmine-browser-runner to test React applications in web browsers. You can follow a similar process to test other kinds of applications that are built with Webpack and Babel.
A complete working example of this setup is also available.
Prerequisites:
- Your application already builds and runs.
- You have Node 18 or newer.
- You’re comfortable editing Webpack config files, or at least willing to give it a try.
- If you want to run your specs in Firefox, a version of
geckodriver that’s
compatible with your version of Firefox is installed somewhere on
$PATH
. - If you want to run your specs in Chrome, a version of
chromedriver that’s
compatible with your version of Chrome is installed somewhere on
$PATH
.
If you used create-react-app to set up your application, you don’t have to eject. However, it can help to eject in a separate copy of your application so that you can see how create-react-app configures Webpack and Babel.
Choose the package management tool you’ll be using:
Adding dependencies
Install these packages if you haven’t already:
$ yarn add --dev jasmine-core jasmine-browser-runner
$ npm install --save-dev jasmine-core jasmine-browser-runner
If you used create-react-app to set up your application, you may also need to add the following dependencies:
cross-env
- The version of
webpack-cli
that corresponds to the version ofwebpack
that’s installed - The same version of
babel-loader
thatreact-scripts
depends on.
You can find out the versions of installed packages with the npm ls
command,
e.g. npm ls webpack
. (This works even if you’re using Yarn to install
packages.)
Configuring Webpack
Next, create a new Webpack configuration file webpack-test.config.js
for your
specs. This should be similar to the one that packages the JavaScript code for
your application. Set the output filename to test.js
and the entry to the
list of spec files. (Tip: use glob
so you don’t have to manually update the
list as you add and remove files.)
For instance, suppose that your main Webpack configuration file looks like this:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.[hash].js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
resolve: {
modules: [__dirname, "src", "node_modules"],
extensions: ["*", ".js", ".jsx", ".tsx", ".ts"],
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: require.resolve("babel-loader"),
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.png|svg|jpg|gif$/,
use: ["file-loader"],
},
],
},
};
A corresponding webpack-test.config.js
might look like this:
const path = require("path");
const glob = require("glob");
module.exports = {
entry: glob.sync("spec/**/*Spec.js?(x)"),
output: {
filename: "test.js",
path: path.resolve(__dirname, "dist"),
},
plugins: [],
resolve: {
modules: [__dirname, "src", "node_modules"],
extensions: ["*", ".js", ".jsx", ".tsx", ".ts"],
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: require.resolve("babel-loader"),
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.png|svg|jpg|gif$/,
use: ["file-loader"],
},
],
},
};
The differences between them are as follows:
-const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
+const glob = require("glob");
module.exports = {
- entry: "./src/index.js",
+ entry: glob.sync("spec/**/*Spec.js?(x)"),
output: {
- filename: "bundle.[hash].js",
+ filename: "test.js",
path: path.resolve(__dirname, "dist"),
},
- plugins: [
- new HtmlWebpackPlugin({
- template: "./src/index.html",
- }),
- ],
+ plugins: [],
resolve: {
modules: [__dirname, "src", "node_modules"],
extensions: ["*", ".js", ".jsx", ".tsx", ".ts"],
The above example assumes that your spec files will be stored in the spec
directory and have names that end in Spec.js
. You’ll need to change the glob
pattern if you want to put the spec files somewhere else. For instance, many
React developers put the spec files next to the code that they test and give
them names ending in .test.js
. To match those files, use src/**/*.test.js
instead of spec/**/*Spec.js(x)
.
Dealing with React imports
Some React codebases have a statment like import React from 'react'
near
the start of every file that contains JSX expressions. Others use a Webpack
plugin to automatically add that at compile time. Check your component source
files. If they don’t have an import statement like the one above, you’ll need
to make sure that webpack-test.config.ts
contains something like this:
test: /\.jsx?$/,
exclude: /node_modules/,
loader: require.resolve("babel-loader"),
+ options: {
+ customize: require.resolve('babel-preset-react-app/webpack-overrides'),
+ presets: [
+ [
+ require.resolve('babel-preset-react-app'),
+ { runtime: 'automatic' }
+ ],
+ ],
+ },
},
{
test: /\.css$/,
Otherwise, errors like ReferenceError: React is not defined
will be thrown
from component source files when you run the specs.
Configuring Babel
If you don’t already have a Babel configuration, create one with the following contents:
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
]
}
Configuring jasmine-browser-runner
Next, create spec/support/jasmine-browser.json
with the following contents:
{
"srcDir": "src",
"srcFiles": [],
"specDir": "dist",
"specFiles": ["test.js"],
"helpers": [],
"browser": {
"name": "firefox"
}
}
The values of specDir
and specFiles
need to correspond to the output path
from the WebPack configuration, in this case dist/test.js
.
If you don’t want to use Firefox, change the browser name to chrome
, safari
,
or MicrosoftEdge
.
Now, add the following to the scripts
section of package.json
:
"test:build": "cross-env NODE_ENV=test npx webpack --config webpack-test.config.js --mode development",
"test:watch": "cross-env NODE_ENV=test npx webpack --config webpack-test.config.js --mode development --watch",
"test": "npm run test:build && jasmine-browser-runner runSpecs",
"test:serve": "npm run test:build && jasmine-browser-runner"
React Testing Library
React Testing Library is currently the most popular choice for rendering React
components in specs and querying the result. Unlike some of the alternatives,
it can run in the browser. Setup is simple. All you need to do is make sure the
@testing-library/react
package is installed. If you used a recent version of
create-react-app
to initialize your application, it might already be there.
If not, install it:
$ yarn add --dev @testing-library/react
$ npm install --save-dev @testing-library/react
See the React Testing Library documentation for more information. The related jasmine-dom matcher library may also be of interest.
Note that most of the React Testing Library docs are written for Jest, so the code samples require some translation before they’ll work in Jasmine. In particular:
- The Jasmine equivalent of
test()
isit()
. - Although there’s a lot of overlap, Jest and Jasmine have different built-in matchers.
- Jest creates an implicit top-level suite in each file. Jasmine doesn’t, so
wrapping the contents of each file in a well-named
describe
is highly recommended.
When translating code samples from Jest to Jasmine, you may find it helpful to refer to the Jasmine tutorial, the list of matchers that come with Jasmine, and the list of matchers that come with jasmine-dom.
Wrapping up
You’re all set. Write your specs using spec filenames that match the Webpack configuration you created earlier, and run them. There are a few different ways to run the specs:
To run the specs once, just run yarn test
npm test
. This will compile the specs
by running test:build
, launch a browser, run the specs in it, and shut down
the browser. The exit code will be 0 if everything passes and nonzero otherwise.
To start a server that will let you run the specs by visiting a web page, run
yarn test:serve
npm run test:serve
. This is
particularly useful for debugging. You may also want to keep
yarn test:watch
npm run test:watch
running to
automatically recompile your code as you make changes.
If you’d rather have yarn test
npm test
use a headless browser,
add --browser=headlessChrome
to the end of the test
script in
package.json
:
"test": "npm run test:build && jasmine-browser-runner runSpecs --browser=headlessChrome",