TDD with react in the browser with mocha, chai and testing library 🔥
Tags: react
Published: 2019-10-30 23:43:56

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:
```javascript
<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>
```
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](https://thepracticaldev.s3.amazonaws.com/i/lr8b3t8xbliv51hdfavx.png)
Let's look at the entry point for *webpack* configuration file:
```javascript
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()
```
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:
```javascript
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)
  }
)
```
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:
```javascript
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'
      }
    )
  ]
}
```
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](https://thepracticaldev.s3.amazonaws.com/i/v9g6g13tdwtuw9usurrs.png)
Let's see at `.babelrc`:
```javascript
{
  "presets":
  [
    "@babel/env",
    "@babel/react"
  ]
}
```
and `package.json`:
```javascript
{
  "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"
  }
}
```
Pay attention at the `scripts` section and to the dependencies section too.

I hope this helps people out there. Thank you.