Testing with Bruno


Updated Jul 27th, 2023

Bruno Antunes has a series of videos on React Testing. The “setup” environment for Jest Typescript and React is a huge time saver. Also may need to refer to RTL docs or Jest docs.

Introduction to Testing: Concepts for Beginners – React.js Testing Tutorial #1

E2E testing: Automated testing instead of manual testing.

Integration vs Unit: Kent C. Dodd’s take, “Don’t care about distinctions…It’s about code satisfying the business requirements.”

Real world example unrelated to code is a car on an assembly line. We test the engine by turning it on and off before putting it in the car. This is an end-to-end test but who cares what type of test.

What is mocking? Using previous example Battery in the car. Test shouldn’t rely on the battery because it might be dead so the battery gets connected to a power outlet so we can test the engine. Safer and more reliable.

When and why to test something? You may use some sort of “risk assessment matrix” which has severity on one axis and probability on another. You should test for functionality that is high probability and high risk. As an example, the Amazon user profile picture customization doesn’t need to be tested but their shopping cart absolutely does.

The pyramid of testing is often taught. This includes E2E at the top, Integration in the middle, and Unit at the bottom. But this is kind of BS, as it is more like a diamond. The creator of Next.js, Guillermo Rauch, has said, “Write tests, not too many. Mostly integration.”

Another line by Kent C. Dodds, “If you have to delete your tests after refactoring then what good did they do to you?!”

Bruno’s personal example of an error in creating tests for an application. He was testing the implementation of “axios” and they moved to “fetch” and it broke all their tests. So do not test implementation details, test the user experience, public interfaces or the public API, (the components and the props they receive).

Avoid refactoring from become a huge paint point.

Setup Testing Env: Jest, React Testing Library, eslint, GitHub Actions -React.js Testing Tutorial #2

Why not use default create-react-app or default next-js setup? Need tests with typescript need to fail compile time?

npx create-next-app yourProjectName --ts
// cd into project
npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init // creates "jest.config.js" file

Note: In NextJS, Bruno always creates an “src” folder and moves the “pages” folder into that folder. I’ve seen a lot of developers do this in Create-React-App but not as much in NextJS. Surprised it doesn’t mess up file-based routing.

Create a “test” script in “package.json” to run your tests.

"test": "jest"

Note: Running “npm t” is the same as “npm run test” just more concise

Create a simple test to make sure jest and ts-jest is working. Make sure it passes with accurate types and fails with inaccurate types. See ts-jest docs here.

export function sum(a: number, b: number) {
  return a + b
// in sum.test.ts file (could be sum.spec.ts)
import { sum } from "./sum"
test("Adding 5 and 2 will return 7", () => {
  expect(sum(5, 2)).toBe(7)

Install react-testing-library

npm install --save-dev @testing-library/react
// but Bruno also likes to install userEvents and testing-library/dom and jest-dom
npm install --save-dev @testing-library/user-event
npm install --save-dev @testing-library/dom
npm install --save-dev @testing-library/jest-dom

Note: as of 6/3/22 @testing-library/react v13+ doesn’t support React <=17. So, for React >=18 -> @testing-library/react >=13+ and for React <=17 -> @testing-library/react <=12.1.5. Source: Stack Overflow.

npm i -D @testing-library/react@12.1.2

Create another test with react component

Test fails in error because with Next, in the “tsconfig.json” file the “jsx”:”preserve” line (cost Bruno a few hours). To solve this create a “tsconfig.jest.json” file to extend “tsconfig.json” and override that “jsx” property with this snippet:

  "compilerOptions": {
    "jsx": "react-jsx"

*Note: this file could be called “tsconfig.test.json” but Bruno likes to have a separate one for cypress and jest.

The test will still fail in error because you need to tell “ts-jest” and jest that you have this new “tsconfig.jest.json” file. Instructions found in the “ts-jest” docs and see in the options menu they have “tsconfig” which shows you how can override the “ts.config.json” file. Copy and paste into “jest.config.ts” file as an additional property:

globals: {
  'ts-jest': {
  tsconfig: './tsconfig.jest.json',  

The test still fails because also in the “jest.config.ts” file, the “testEnvironment” property is set “node”, and needs to be changed to “jsdom” which we need to install in order to use things like, “.toBeInTheDocument()”

import { render, screen } from "@testing-library/react"
import Hello from "./Hello"
test("renders 'Hello World'", () => {
  render(<Hello />)
  const myElement = screen.getByText("hello World")

Search google for “setupFilesAfterEnv,” to have jest run one file that you have or code or whatever before running any tests. And it only runs once. Copy and paste this line from docs:

setupFilesAfterEnv: ['./src/jest.setup.ts']

Note: As mentioned earlier Bruno has his components and pages in an “src” folder so he added this to the file path.

Now create a new “jest.setup.ts” file, and add the following statement:

import '@testing-library/jest-dom'

At this point you should have configuration set for Jest and React Testing Library.


Bruno says the ESLint config is just as important as the Jest and React Testing Library. Docs here.

Run “npm init @eslint/config” and ESLint will start to ask a few questions in your console.

Note: Bruno used a “.js” file but it seems there is less warnings with a “.json” and the syntax is very similar.

In the newly created and automatically generated “.eslintrc.js” file, in the “extends” property add “next”, and “next/core-web-vitals” as values in that array.

Then delete the “env” object and the “plugins” object.

Add back a plugin in the “extends” area to enforce even more type checking:

Make sure there is a “lint”: “next lint” script in the “package.json” file and run.

Then back in “eslintrc.js” go to the “parserOptions” field and add “project”: “./tsconfig.json”

Note: I ended up with the following error after this step: “Error: Parsing error: “parserOptions.project” has been set for @typescript-eslint/parser.” Also:
The file does not match your project config: pages\api\auth[…nextauth].js.
The file must be included in at least one of the projects provided.

Adding “”**/*.js”” to the “include” property array inside the “tsconfig.json” file solved the warning”. I also think this may just need to be a pages\api\auth[…nextauth].ts file.

Disable an ESlint rule that Bruno doesn’t like that you must return a type on every single function, (may do this a few times).

"rules": {
  "@typescript-eslint/explicit-module-boundary-types": "off"

At this point I ran into an error that “Failed to load config “next” to extend from” and this was fixed by installing:

npm i --save-dev eslint-config-next

But then running the linter I got a ton of ESLint errors because I am implementing this whole process on a deployed project. Great that it is working but bad that there is a lot of work to be done. These are all the rules disabled, using the process above, in order to get to “No ESLint warning or errors.” A lot.

    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-unsafe-assignment": "off",
    "@typescript-eslint/no-unsafe-member-access": "off",
    "@typescript-eslint/no-unsafe-argument": "off",
    "@typescript-eslint/no-unnecessary-type-assertion": "off",
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/restrict-template-expressions": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-floating-promises": "off",
    "@typescript-eslint/no-unsafe-call": "off",
    "react/no-unescaped-entities": "off",
    "@typescript-eslint/require-await": "off",
    "react/jsx-key": "off",
    "no-inner-declarations": "off",
    "react-hooks/exhaustive-deps": "off",
    "@typescript-eslint/restrict-plus-operands": "off",
    "@typescript-eslint/no-misused-promises": "off",
    "react-hooks/rules-of-hooks": "off",
    "no-empty": "off",
    "no-useless-catch": "off",
    "react/jsx-no-comment-textnodes": "off",
    "testing-library/render-result-naming-convention": "off",
    "react/jsx-no-target-blank": "off",
    "@typescript-eslint/no-unsafe-return": "off",
    "@typescript-eslint/no-inferrable-types": "off",
    "prefer-const": "off"

Here is the link for disabling rules.

Even after disabling the rules this way they are still showing up.

Also, not sure why my “eslintrc.js” has properties not wrapped in quotes unlike Bruno. Even if I add them in prettier is stripping them out.

From NextJS docs, “ESLint also contains code formatting rules, which can conflict with your existing Prettier setup. We recommend including eslint-config-prettier in your ESLint config to make ESLint and Prettier work together.”

The above step resolved the issue

This next step is only needed if using a “src” folder like Bruno. Configure NextJS project structure with ESLint because ESLint is only looking at a few folders by default (“pages”, “components”, and “lib”) and not the “src” folder. Resolve by adding “–dir src” to the “lint” script in the “package.json” file.

Now add the plugins for jest and RTL to get ESLint errors/warnings specific to these libraries and use the proper matchers. These are small details but can save you time.

Install via “npm install eslint-plugin-jest –save-dev”

Want use both “recommended” and “style” config since “all” is not fully stable at the time of recording.

This is done by adding to the “.eslintrc.js” file, in the “extends” property, “plugin:jest/recommended” and “plugin:jest/style.”

Bruno used an example of “fit” instead of “it” to focus test and have the ESLint error throw a warning.

Install the react-testing-library eslint plugin and add the corresponding plugin value to the “eslintrc.js” file.

npm install --save-dev eslint-plugin-testing-library

An example of something this plugin shouldn’t like is using screen.debug() which is similar to a console.log triggering an “Error: Unexpected debug statement” from ESLint.

Note: as of 7/27/2022 I received the following warning: Test environment jest-environment-jsdom cannot be found. Make sure the testEnvironment configuration option points to an existing node module. As of Jest 28 “jest-environment-jsdom” is no longer shipped by default, make sure to install it separately.

npm i jest-environment-jsdom

Continuous Integration Pipeline

Create a lint stage so we can use GitHub Actions to run ESLint on every commit we do.

npx mrm@2 lint-staged

Note: this installs “husky,” sweet package I’ve seen before.

npm install lint-staged prettier --save-dev

Now in the “husky/pre-commit” file change “npx lint-staged” to be “npm run lint-staged” and then in “package.json” file and confirm you now have a “prepare” script that runs “husky install” and add a “lint-staged” script that runs “lint-staged”

Down in the “package.json” file, in the “lint-staged” property, update “js”: “eslint –cache –fix” to be “*.(tsx|ts)”: “eslint –cache –fix”, and add underneath “*”: “prettier –write –ignore-unknown”

Bruno shows how making a commit will run prettier.

Note: I’m not sure why he doesn’t just run prettier on file save but instead on commit?

Note from NextJS docs: “If you would like to use “next lint” with lint-staged to run the linter on staged git files, you’ll have to add the following to the “.lintstagedrc.js” file in the root of your project in order to specify usage of the --file flag.”

From GitHub repo, click on Actions and create a new workflow by clicking on “More continuous integration workflows” and selecting “Node.js” update the name of the “.yml” file to whatever you desire.

Change branches from [main] to “*”

Change “runs-on” from “ubuntu-latest” to ${{matrix.os}} and define that matrix by adding, to the matrix property, a new “os” property and set to [ubuntu-latest, windows-latest, macos-latest ]

In the “steps:” property, change the “run:” that is set to “npm run build –if-present” to “npm run lint” and change “npm run test” to “npm run test:ci”

Then click start commit and add a commit “add github actions that will fail”

Google “jest code coverage threshold” to fail build if it doesn’t meet a given code coverage threshold. Copy code form Jest docs and paste into “jest.config.js” and update necessary values, (70% versus 80% etc.).

In the “package.json” file, add “test:ci” script that runs “jest –coverage –silent –ci”

React Testing Library for Beginners: React.js Testing Tutorial #3

Test Driven Development: TDD

Red Green Refactor

The goal for the React testing library is to test React components but focus less on the implementation details and more on the way your software is used. To prevent getting burned on a change. For example, class-based components moving to functional components. RTL is a replacement for Enzyme.

Grab/Query elements from the screen. There is a priority recommended (see here). The best is getByRole, then getByText. Using the “data-testid” and “getByTestId” is the last priority but has its use cases.


Do your tests based on the expected outcome, not based on the implementation.

Add a new file “Counter.spec.tsx” or “Counter.test.tsx”

test() is just syntactic sugar for it()

it("defaultCount=0, and + clicked then counter = 1", () => {
  render(<Counter defaultCount={0} description="My Counter"/>)
  expect(screen.getByText("Current Count: 0"))
  expect(screen.getByText(/My Counter/i)).toBeInTheDocument

Note: The test above uses regex in forward brackets and the /i is saying case sensitivity does not matter.

.todo can help you delay your test. they will show in separate color in the terminal.


grouping using describe() create sections and this helps to find tests easier.

Use beforeEach() to run every time before the rest of the group. Although in Bruno’s example he renders the component in the “beforeEach” and this will actually throw an RTL linting error:

Forbidden usage of render within testing framework beforeEach setup “eslinttesting-library/no-render-in-setup”

There is a Kent Dodd’s article on why to avoid nesting here.

Add an “aria-label” attribute to the button and grab with getByRole

Add new feature using TDD.

React Async Testing using React Testing Library for Beginners: React.js Testing Tutorial #4

Testing asynch code is where people struggle the most in their React testing journey.

What is async code? Clicking increment button and it calls to the server to see what the next number is, introducing some delay into your code. Other examples include using a setInterval or setTimeout or using a debounce when entering text into a text field. Need to wait for an action to happen before you test your code.

We don’t need to use “sleeps” like you may see in Selenium because RTL has “findBy Queries” which go asynchronously as compared to the “getBy” which go synchronously.

Note: There are “getBy, findBy, queryBy, getAllBy, findAllBy, queryAllBy” query methods.

“findBy” queries work when you expect an element to appear but the change to the DOM may not happen immediately.

The default interval is 50ms. However it will run your callback immediately before starting the intervals. The default timeout is 1000ms. This means that react-testing will test to see if something is in the DOM, and if not, will check again in 50ms.

Example 1:

use findBy query

convert previous example to have the click action in a “setTimeout” and this makes some tests fail because they immediately expect the counter to be one.

Note: How can you focus a test to have it run first? Use “.fit” instead of “.it” but know that the other “beforeEach” will still run.

fit('renders "Current count: 1"', async() => {
  await waitFor(() => expect(screen.getByText('Current Count: 1')).toBeInTheDocument())

Note: to only run the tests in one file use the command “npm t — yourFilename.spec.tsx”

Bruno prefers, when here are a lot “beforeEach”(s) depending on each other, to have the “beforeEach” to have all the logic regarding the waiting and doing actions. This way all you tests can just have the synchronous stuff and not in each “.it()” although not everyone will agree with this strategy.

~12.5 minutes

How you can wait for elements to be removed from the screen? For example, you do an http call and you have a spinner on the screen. How do you make sure the spinner was there and now you are waiting for it to disappear only when it disappears you want your test to pass?

Example 2: Add an element to act as a spinner that disappears once the counter gets to 15, 300ms after the http is done disappear.

Add some state to act as loading state.

it("renders too big, and will disappear after 300ms", async () => {
  await waitForElementToBeRemoved(() => screen.queryByText("I am too small"))

Note: waitForElementToBeRemoved will check to see if the element was ever there and then remove it. In the case of a loading spinner it may not show until a fecth button is clicked and then when the http request is finished updates the state to remove the loading spinner.

You set a variable named “id” using let. It is later assigned to a “setTimeout.” What type should you use next to id?

useEffect(() => {
  let id = NodeJS.Timeout
  id = setTimeout(() => setLoading(false), 300)
  return function tearDown() {
}, 300)

How can run a single test in a single file using jest? instead of runnign all of your tests, or even all of the tests in one file?

use the fit() keyword and npm t –yourFileName.spec.tsx

React may throw warning if we are trying to access/test a component after is has been unmounted/destroyed. Need a cleanup function that clears the timeout.

As an example let’s say you increment a value in state when a button is clicked but that update function is in a setTimeout. Your tests will fail because the value does not update immediately.

When you focus a test with the .fit keyword will the beforeEach’s still run? Yes

findBy returns a promise so can use await keyword

default interval 50ms
default timeout 1000ms

syntactic sugar on top of waitFor

getBy and findBy queries return what when they don’t find a match?

throw an error. queryBy just returns null.

Mocking React Components and Functions using Jest for Beginners – React.js Testing Tutorial #5

Example #1:

Always want Math.random() to return a specific value so you can run your test.

jest.spyOn(Math, "random").mockReturnValue(0.5)
// toHaveBeenCalledTimes(1)
// toHaveBeenCalledWith()
// toHaveBeenLastCalledWith()

“Spies keep for forever counts from the past so a lot of times you need to clear a mock (telling it to forget about the past) to make sure other tests don’t fail. This is often done in a “beforeEach()”.

// mockClear()
// mockReset()
// mockImplementation is an alternative to .mockReturnValue()
beforeEach(() => whatEvs.mockClear())

Example #2:

Two parts, the first is just a button that calls an “onMoney” function. We want to test that when we click the button the function is called. There are two ways to do this. The first way involves asynchronous testing using “done” keyword. Pass “done” as a parameter and then later calling “done()”. The second way is to use from jest the “jest.fn()” syntax that can be called with “.toHaveBeenCalledTimes(1)” or “toHaveBeenCalledWith(33)”

//first way
describe('MyComponent', () => {
  it('renders Material-UI grid with columnDefs and rowData', (done) => {
    const myOnMoney = (n: number) => {
    render(<Example2 onMoney={myOnMoney} />);
    fireEvent.click(screen.getByRole('button', { name: 'Give me 33 dollars' }));
//second way
describe('MyComponent', () => {
  beforeEach(() => {
  it('renders Material-UI grid with columnDefs and rowData', () => {
    const myOnMoney = jest.fn();
    render(<Example2 onMoney={myOnMoney} />);
    fireEvent.click(screen.getByRole('button', { name: 'Give me 33 dollars' }));

The second part of the example is to mock a “DataGrid” component from material UI in which we mock this module to just render a div that says table. We also want to make sure we pass the appropriate props. As long as we pass the expected props we fully trust that the material UI team has done their tests, and so we don’t have to more than make sure we are sending the necessary props.

Bruno also mentioned the use of expect.objectContaining() to have required fields for the column of the DataGrid, helpful if you only care abouyt one or two of the properties you don’t need to list them all.

Another important thing to prevent this test from failing is to add an empty pair of curly brackets as a dependency array. This is because when react calls our components it does so in a context.

import { Example2, rows } from './Example2';
import { fireEvent, render, screen } from '@testing-library/react';
import { mocked } from 'ts-jest/utils';
import { DataGrid } from '@material-ui/data-grid';
jest.mock('@material-ui/data-grid', () => ({
  ...jest.requireActual('@material-ui/data-grid'), // get the module as is
  DataGrid: jest.fn(() => <div>Table</div>),
const mockedDataGrid = mocked(DataGrid);
// with the line above, now we can do the same things with the function
// toHaveBeenCalledTimes(1) and toHaveBeenCalledWith() 
describe('MyComponent', () => {
  beforeEach(() => {
  it('renders Material-UI grid with columnDefs and rowData', () => {
    const myOnMoney = jest.fn();
    render(<Example2 onMoney={myOnMoney} />);
    fireEvent.click(screen.getByRole('button', { name: 'Give me 33 dollars' }));
  it('renders table passing the expected props', () => {
    render(<Example2 onMoney={jest.fn()} />);
        rows: rows,
        columns: [
          expect.objectContaining({ field: 'id' }),
          expect.objectContaining({ field: 'firstName' }),
          expect.objectContaining({ field: 'lastName' }),
          expect.objectContaining({ field: 'age' }),
        pageSize: 5,
        checkboxSelection: true,

Note: About jest.fn(), we can pass this as a prop to simplify our tests, and just test the links between code by erasing the actual implementation of a function. See below:

it("render the comp passing the expected props", () => {
  render(<WhatEvCompExpectingProps onMoney={jest.fn()} />)

Example #3:

Similar to example 2 but about “swipeable drawer” from material-ui. Github. He received request to specifically mock this component.

import { render, screen } from '@testing-library/react';
import React from 'react';
import { MyDrawer } from './Drawer';
import user from '@testing-library/user-event';
jest.mock('@material-ui/core', () => ({
  SwipeableDrawer: jest.fn(() => <div>HELLOOOOOO</div>),
describe('Drawer', () => {
  it('shows no "Hello YouTube!"', () => {
    render(<MyDrawer />);
  it('clicking on "Open Drawer" Button shows "Hello YouTube!"', () => {
    render(<MyDrawer />);
    user.click(screen.getByRole('button', { name: 'Open Drawer' }));

I saw this code and thought it may be useful:

expect(screen.queryByText("Hello Youtube!")).not.toBeInTheDocument()

Also this:


Example #4:

Similar to example 3 but we are going to mock something from our own application.

import { render, screen } from '@testing-library/react';
import { mocked } from 'ts-jest/utils';
import { MyDrawer } from '../Example3/Drawer';
import { Example4 } from './Example4';
mocked(MyDrawer).mockImplementation(() => <div>mocked: drawer</div>);
describe('Example4', () => {
  it('renders MyDrawer', () => {
    render(<Example4 />);
      screen.queryByText('Hello Drawer Component!')
    expect(screen.getByText('mocked: drawer')).toBeInTheDocument();

Example #5:

When requesting mock from very complex component (very deeply nested?). Uses “mocks” folder.

The test file:

import { render, screen } from '@testing-library/react';
import { Example5 } from './Example5';
describe('Example 5', () => {
  it('renders very complex component', () => {
    render(<Example5 />);
    expect(screen.getByText('SIMPLE VERSION')).toBeInTheDocument();

The component file:

import { VeryComplex } from '../../VeryComplex/DeepFolder/DeeperFolder/VeryComplex';
export function Example5() {
  return (
      <VeryComplex />

Mock HTTP calls using Fetch or Axios – Mock Service Worker – React.js Testing Tutorial #6

Avoid using mocks for http calls. Use MSW, “mock service worker” library, (mswjs.io).

Avoid mocking axios or fetch with syntax like this:

// jest.spyOn(window, 'fetch')
const mockedAxios = mocked(axios);
const mockedAxiosGet = mocked(mockedAxios.get)
const mockedAxiosPost = mocked(mockedAxios.post)
describe("PhotoList", () => {
 beforeEach(() => {
   data: [
       id: 1,
       thumbnailUrl: "/photo1.png",
       title: "Hello World",
       favorite: false,
     [] as Photo[],

The reason to use MSW is making HTTP calls with Axios vs Fetch is an implentation detail so don’t we don’t base our test on that but base on the http call itself. If we ever change from fetch to axios we don’t invalidate our tests.

Install the MSW library and a polyfill:

npm install --save-dev msw whatwg-fetch

Using fetch with MSW will require a polyfill imported into your “jest.setup.ts” file.

import "whatwg-fetch"

Note: in order to have a “jest.setup.js” file you need to add into your “jest.config.js” file the following line:

setupFilesAfterEnv: [".src/jest.setup.ts"]

Here is the link to the examples.

Will use beforeAll() and afterAll() to start and stop server and likely afterEach() as well.

MSW Docs: How to Create our Mock Server:

https://mswjs.io they have a REST API example using TypeScript:

import { setupWorker, rest } from 'msw'
interface LoginBody {
  username: string
interface LoginResponse {
  username: string
  firstName: string
const worker = setupWorker(
  rest.post<LoginBody, LoginResponse>('/login', (req, res, ctx) => {
    const { username } = req.body
    return res(
        firstName: 'John'

MSW HTTP Get Call – Create Mock Server:

Slightly different for Node:

import {setupServer} from "msw/node"
import {rest} from "msw"
const server = setupServer(rest.get('endpoint', (req, res, ctx) => {
  return res(ctx.json([]))

Example #1: Spinner test and add delay to MSW response

Example #2: Type “Bruno” in search field and use query parameters

May need to wait for multiple calls to end to finish loading spinner. Instead of using loading state with true and false use how many calls are pending using a number. Another way is to cancel previous calls as they are made.

Example #3: Return 500 status code with MSW

Example #4: HTTP Post request with MSW

Uses .resetHandlers() and after seeing test passes, switches to fetch implementation and the test still passes.

React Hooks SWR: Test components that useSWR – Mock Service Worker – React.js Testing Tutorial #7

Using fetch with MSW will require polyfill imported into jest.setup.ts file.

Create setupServer()

SWR does caching so may need to override with {dedupingInterval: 0, provider: () => new Map()} and to not repeat yourself you have two options: AllTheProviders or instead create a customRender (both from React Testing Lib).

Testing React Forms – React Testing Library – React.js Testing Tutorial #8

multistep form

fireEvent.change but instead user from “@testing-library/user-event”


formik validations are asynchronous, so there is a very slight delay.

refactor to use toHaveErrorMessage, require aria-errormessage and an id