Setting up Jest + React-Testing-Library
Tags: react, testing, typescript, javascript
Published: 2019-06-29 15:47:09


In the past few months, I've had the immense pleasure to work with what I can say is my favorite library to use in the React + Friends environment, react-testing-library. This library is a piece of art.

There hasn't been a library more intuitive, easy to set up and more beginner friendly. In some occasions, it has been even a great way to break in a developer into a new project. With this guide, my aim is to share what I have learned in the process of configuring tooling for different projects and sort of the best practices that I have personally adopted. We will walk through the steps to get your environment setup with create-react-app and setting up a React project from scratch (jest*, web-pack, babel, etc…).
More than a how-to test guide, this guide is a step by step walkthrough on how to set up your testing environment in a React application. For learning how to write a unit test there are plenty of guides by more knowledgeable people than myself going over that topic. However, I will say that for me, the best way to become better at testing is to actually write tests. One of the biggest struggles I see both new and old developers who haven't written test, is learning the distinction between what Jest's role is and what react-testing library's role. In my own experience, the way to learn that distinction is by repetition, getting stuck, doing research, reiterating.

Eager to hear feedback from the community!

## Index:

- Directory file structure & conventions I've picked up
- Getting started with Jest + RTL and create-react-app
- Getting started with Jest + RTL from scratch
- Config to setup with Typescript
- Examples
- Curated resources to help you get started with RTL




## File structure:

Article I wrote on How I structure my React Apps (not with hooks):
https://blog.usejournal.com/how-i-structure-my-react-apps-86e897054593

Best practices and convention has been for the longest time, even before react was around, to create a folder `__ test __` and just put your test files inside that folder. I have kinda done things differently and this is absolutely just personal preference. As I kept working with it the following system it kinda just stuck and the team I work with and my self pretty much enjoy it (I think!).

## The typical file structure in my projects:

```
- node_modules
- public
- src
  - components
    - MyComponent
      - MyComponent.jsx
      - MyComponent.styles.js
      - MyComponent.test.js      // here is what I do different
    - index.js            // source of truth for component export
  - utils
    - helpers.js
  - pages
  - App.jsx
  - App.test.jsx
  - App.styles.js
  - index.js
```

As I point out in the comment above. This is my biggest personal deviation from the popular convention. It just seems to me that in the age of component driven development it makes more sense for me to create this sort of encapsulated environments for your components (most important thing is to be consistent and work with what makes you comfortable 😁). Adding one __test__ folder for each component you have, which in a large code base, with a lot of components and component variations, it seems like something that just not DRY. Additionally, I do not find any personal benefit behind adding that folder. Besides when jest crawls your root directory and looks for files to run it's not looking for a folder in particular (well, depends on your jest's RegEx pattern).


##Naming and casing conventions:
```
- PascalCase for component file name and folder name
- Generally, I want to indicate if my components are container or component.
  Containers will usually be class components that contain state and logic,
  whereas components will house the actual content, styling and receive props from the container. 
  Example:
  - `MyComponent.container.js`
  - `MyComponent.component.js`
  - `MyComponent.jsx`          // if no container
  - `MyComponent.styles.js`
- lowerCamelCase for Higher Order Component file and folder name
- lowercase for all other root directory folders. For example: `src`, `components`, `assets`
```

---

##Some conventions worth noting

##Describe method:
```jsx
describe('My component', () => {
  // group of test()
})
```

Describe method is one of what jest calls Globals methods, which you don't have to import or require to use. The describe statement, in particular, is used to group similar test together.

##Test method
```jsx
test('some useful message', () => {
   // logic
}, timeout) // timeout is optional
```

Test functions are the bread and butter though. This is the function that actually runs your tests. According to Jest's documentation, the first argument is the name of the test, the second argument is the callback where you add your testing logic (assertions, etc.), and the third argument, which is optional, is the time out.

The test function also has an alias that can be used interchangeably it(): `it('test', () => {})`

---

##Getting started jest and RTL with CRA:

Full disclosure. I love using CRA it setups everything for you and reduces the amount of technical overhead you will get over time as dependency versions fall behind. With react-scripts, you pretty much just have to worry about that part.

`npx create-react-app ProjectName`

`npx create-react-app ProjectName --typescript`

right off the bat, the first thing I do is install dependencies needed:

`npm install --save-dev @testing-library/jest-dom`

`npm install --save-dev @testing-library/react`

In the `package.json` file I add the following script:
```jsx
"test": "jest -c jest.config.js --watch"
```

Quick note: first thing when I start a new react project is to add those dependencies + `styled-components` and my `types` if need be.

Testing library documentation defines jest-dom as a companion library for React Testing Library that provides custom DOM element matchers for Jest. In essence, it is the dependency that provides statements (or matchers*) such as `toHaveStyles` or `toHaveAttribute`.

Example: 
`expect(Component).toBeInTheDocument()` <- matcher

Once your project is created, inside my src folder I add a file called `setupTests.js`.

```
- src
  - components
  - App.js
  - setupTests.js
```

The `setupFiles` is executed before the test framework is installed in the environment. For our case, it is especially important, because it will allow us to run the correct imports before the tests are executed. This gives us the opportunity to add a couple of imports.

So in your setupTests.js file:
```jsx
import '@testing-library/jest-dom/extend-expect'
```
And that's it for that file :).

This is all you need to get up and running with `jest` and `react-testing-library`!

---

##Getting started on jest and RTL with a React app from scratch:

This part will be a little longer since there are more tools to cover and configure. In a way, we will walk through my step by step process to build a react application from scratch. `create-react-app` does abstract a lot of the configuration complexity and it does it really well, now we have to configure our babel and for our case most importantly the jest configuration. Higher overview the jest config takes care of making sure that `jest` knows where to look for, what to look for and how to execute it.

A Great resource to setup your React App from scratch:
https://blog.bitsrc.io/setting-a-react-project-from-scratch-using-babel-and-webpack-5f26a525535d

##Directory structure
```
- node_modules`
- public
  - index.html
- src
  - components
    - MyComponent
      - MyComponent.jsx
      - MyComponent.styles.js
      - MyComponent.test.js      // here is what I do different
    - index.js             // source of truth for component export
  - utils
  - pages
  - App.jsx
  - App.test.jsx
  - App.styles.js
  - store.js
  - index.js
- webpack.config.js
- jest.config.js
- .gitignore
- .eslintrc
- .prettierrc
```
##index.html:

```html
<!DOCTYPE html>
  <html lang="en">    
    <head>        
      <meta charset="UTF-8" />        
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />        
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />                                    
      <title>React JS + Webpack</title>
    </head>    
    <body>        
      <div id="root"></div>    
    </body>
  </html>
```
##App.js
```jsx
import React from 'react';

const App = () => <h1>Hi World</h1>;

export default App;
```

##index.js
```jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
```

##webpack.config.js:
```js
const webpack = require("webpack");

// plugins
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "./main.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ["file-loader"]
      },
      { test: /\.jsx$/, loader: "babel-loader", exclude: /node_modules/ },
      { test: /\.css$/, use: ["style-loader", "css-loader"] }
    ]
  },
  devServer: {
    contentBase: "./dist",
    hot: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      filename: "./index.html"
    }),
    new webpack.HotModuleReplacementPlugin()
  ]
};
```

##jest.config.js:
```jsx
module.export = {
  roots: ['<rootDir>/src'],
  transform: {
    '\\.(js|jsx)?$': 'babel-jest',
  },
  testMatch: ['<rootDir>/src/**/>(*.)test.{js, jsx}'], // finds test
  moduleFileExtensions: ['js', 'jsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/public/'],
  setupFilesAfterEnv: [
    '@testing-library/jest-dom/extend-expect'', 
    '@testing-library/react/cleanup-after-each'
  ] // setupFiles before the tests are ran
};
```

---

##MyComponent.js:
```jsx
import React from 'react'
import styled from 'styled-components'

const MyComponent = props => {
  
  return (
    <h1>`Hi ${props.firstName + ' ' + props.lastName}!`</h1>
  )
}
export default MyComponent

```
##MyComponent.test.js:
```jsx
import React from 'react'
import { render, cleanup } from '@testing-library/react'
import MyComponent from './MyComponent'

afterEach(cleanup)

describe('This will test MyComponent', () => {
  test('renders message', () => {
     const { getByText }= render(<Mycomponent 
                                 firstName="Alejandro"
                                 lastName="Roman"
                              />)

     // as suggested by Giorgio Polvara a more idiomatic way:
     expect(getByText('Hi Alejandro Roman')).toBeInTheDocument()
})
```
##input example:
```jsx
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Input from './Input'


test('accepts string', () => {
  // I try to avoid using data-testid as that is not something a user would
  // use to interact with an element. There are a lot of great query and get 
  // methods
  const { getByPlaceholderText } = render(<Input placeholder="Enter
                                           Text" />);
  const inputNode = getByPlaceholderText('Search for a problem or application name');
  
  expect(inputNode.value).toMatch('') //tests input value is empty

  // if you need to perform an event such as inputing text or clicking
  // you can use fireEvent
  fireEvent.change(inputNode, { target: { value: 'Some text' } }));
  
  expect(inputNode.value).toMatch('Some text'); // test value 
                                                // is entered
});
```


#Typescript config

##tsconfig.json:
```jsx
{
  "include": [
    "./src/*"
  ],
  "compilerOptions": {
    "lib": [
      "dom",
      "es2015"
    ],
  "jsx": "preserve",
  "target": "es5",
  "allowJs": true,
  "skipLibCheck": true,
  "esModuleInterop": true,
  "allowSyntheticDefaultImports": true,
  "strict": true,
  "forceConsistentCasingInFileNames": true,
  "module": "esnext",
  "moduleResolution": "node",
  "resolveJsonModule": true,
  "isolatedModules": true,
  "noEmit": true
  },
  "include": ["./src/**/*"],    
  "exclude": ["./node_modules", "./public", "./dist", "./.vscode"]
}
```

##jest config:
```jsx
module.exports = {
  roots: ['<rootDir>/src'],
  transform: {
    '\\.(ts|tsx)?$': 'babel-jest',
  },
  testMatch: ['<rootDir>/src/**/?(*.)test.{ts,tsx}'],   // looks for your test
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/public/'],
  setupFilesAfterEnv: [
    'jest-dom/extend-expect',
    '@testing-library/react/cleanup-after-each'
  ]  // sets ut test files
};
```
##webpack config:

```js
const path = require('path')

// Plugins
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: {
        dev: './src/index.tsx',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].bundle.js',
    },
    devServer: {
        compress: true,
        port: 3000,
        hot: true,
    },
    devtool: 'source-map',
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
    module: {
        rules: [
            /**
             * Gets all .ts, .tsx, or .js files and runs them through eslint
             * and then transpiles them via babel.
             */
            {
                test: /(\.js$|\.tsx?$)/,
                exclude: /(node_modules|bower_components)/,
                use: ['babel-loader'],
            },

            /**
             * All output '.js' files will have any sourcemaps re-processed by
             * source-map-loader.
             */
            { test: /\.js$/, enforce: 'pre', loader: 'source-map-loader' },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
        }),
    ],
}
```




---

Extra resources:

Some resources that helped me learn different parts of using React testing library:

Docs:

https://testing-library.com/docs/react-testing-library/intro

Create-react-app: https://www.youtube.com/watch?v=Yx-p3irizCQ&t=266s

Testing redux: https://www.youtube.com/watch?v=h7ukDItVN_o&t=375s

Component unit testing: https://www.youtube.com/watch?v=KzeqeI046m0&t=330s

Mocking and more component testing: https://www.youtube.com/watch?v=XDkSaCgR8g4&t=580s

Portals: https://www.youtube.com/watch?v=aejwiTIBXWI&t=1s

Mocking: https://www.youtube.com/watch?v=9Yrd4aZkse8&t=567s

Test async components: https://www.youtube.com/watch?v=uo0psyTxgQM&t=915s