DEV Community

roggc
roggc

Posted on • Updated on

TDD with react in the browser with mocha, chai and testing library 🔥

The combination which I have found useful for doing TDD with react in the browser is the one with mocha, chai and testing library.
Let's begin by showing you the HTML file which serves as a template for webpack to generate the index.html file in the output folder:

<html>
<head>
    <meta charset="utf-8">
    <title>React TDD</title>
    <link href="https://unpkg.com/mocha/mocha.css" rel="stylesheet" />
</head>
<body>
  <div id="mocha"></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This file will be used by the html-webpack-plugin to load an index.html file in the output folder. The screen when running the tests in the browser will look like this:
Alt Text
Let's look at the entry point for webpack configuration file:

import {mocha} from 'mocha'
import app from './comps/app/app.spec'
import header from './comps/header/header.spec'
import footer from './comps/footer/footer.spec'
import menu from './comps/menu/menu.spec'
import home from './comps/home/home.spec'

mocha.setup('bdd')
mocha.checkLeaks()
app()
header()
footer()
menu()
home()
mocha.run()
Enter fullscreen mode Exit fullscreen mode

This is index.js file under src folder. As I have said it is the entry point for webpack to produce the bundle. As you can see it imports mocha from 'mocha' and then imports all the tests that it will run. Last command, mocha.run() is where mocha starts to run the tests and puts the output into the html document.
Let's see at one of the tests for an example:

import React from 'react'
import {App} from '../app/app'
import 'chai/register-should'
import {BrowserRouter as Router} from 'react-router-dom'
import {render,fireEvent,cleanup} from '@testing-library/react'

export default
()=>
describe(
  'menu',
  ()=>
  {
    it('has home and about links that redirects to respectives routes in content component',
  ()=>
{
  const {queryByTestId}=render(<Router><App/></Router>)
  should.exist(queryByTestId('home'))
  should.not.exist(queryByTestId('about'))
  fireEvent.click(queryByTestId('about-link'))
  should.not.exist(queryByTestId('home'))
  should.exist(queryByTestId('about'))
  fireEvent.click(queryByTestId('home-link'))
  should.exist(queryByTestId('home'))
  should.not.exist(queryByTestId('about'))
})
afterEach(cleanup)
  }
)
Enter fullscreen mode Exit fullscreen mode

In this file (menu.spec.js in src/comps/menu folder) we are testing the menu component. menu component has two links at the moment which must change the content of the page. We render an App component and then search for a home component and an about component. The first (home page) must exist by default, while the second (about page) appears when clicking a link in the menu component. You see how we make use of afterEach and cleanup.
It rests us to see the webpack configuration file:

import HtmlWebpackPlugin from 'html-webpack-plugin'
import {CleanWebpackPlugin} from 'clean-webpack-plugin'

export default
{
  entry:'./src/index.js',
  module:
  {
    rules:
    [
      {
        test: /\.m?js$/,
        exclude: /(node_modules)/,
        use:
        {
          loader: 'babel-loader'
        }
      }
    ]
  },
  node:
  {
    fs:'empty'
  },
  devServer:
  {
    historyApiFallback: true
  },
  plugins:
  [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin
    (
      {
        template:'./src/public/index.html'
      }
    )
  ]
}
Enter fullscreen mode Exit fullscreen mode

We use two plugins (one for cleaning output folder each time). Our entry point is ./src/index.js file. We use babel-loader for dealing with js files. We use the trick node:{fs:'empty'} to avoid a problem that arises with mocha and fs. And we use devServer:{historyApiFallback:true} just in case because we are dealing with react-router-dom and simulating clicks in the tests which changes routes so we do not want problems (this option is used when you use webpack-dev-server and you don't want to receive a message as cannot get /about when you are in /about and do a reload of the page).
Let's see at the file structure of the project:
Alt Text
Let's see at .babelrc:

{
  "presets":
  [
    "@babel/env",
    "@babel/react"
  ]
}
Enter fullscreen mode Exit fullscreen mode

and package.json:

{
  "name": "test2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "transpile:wp": "babel wp -d wpt",
    "start": "npm run transpile:wp && webpack-dev-server --mode=development --config wpt/webpack.config.js",
    "build": "npm run transpile:wp && webpack --mode=production --config wpt/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^6.2.2",
    "webpack-dev-server": "^3.9.0"
  },
  "dependencies": {
    "@babel/cli": "^7.6.4",
    "@babel/core": "^7.6.4",
    "@babel/preset-env": "^7.6.3",
    "@babel/preset-react": "^7.6.3",
    "@testing-library/react": "^9.3.1",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "html-webpack-plugin": "^3.2.0",
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-router-dom": "^5.1.2",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.9"
  }
}
Enter fullscreen mode Exit fullscreen mode

Pay attention at the scripts section and to the dependencies section too.

I hope this helps people out there. Thank you.

Top comments (0)