React Testing

React Testing using Testing library and Jest

Udemy Course

What is Jest?

Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and more!

What is Jest-DOM?

  • Jest Dom comes with create-react-app

  • It uses src/setup.test.js and the Jest DOM is available on all these files ending with .test.js

  • The above point means that the Jest DOM matchers are available in .test.js files where Jest DOM provides some of the methods that will test the screen elements -> "Tests if element is present on DOM or not" and many more methods related to screen/UI which is DOM.

  • The normal Jest doesn't do the testing of screen elements. It might do test of Javascript related stuff, for example, jest can test how many elements are present in an array. On the other hand, Jest DOM can test if UI shows "Learn React" or not.

  • Simply put, because of Jest DOM, the DOM matchers for Jest is available on .test.js files

Jest docs

What is React Testing Library?

  • Provides virtual DOMs for tests - Any time we are running tests without a browser, we need to have a virtual DOM so we can do things like click elements and we can see if virtual DOM behaves like it should do.

  • Jest is a test runner. Jest is responsible for

    • Finding Tests

    • Running the tests

    • Determining whether the tests pass or fail

  • Both React Testing Library and Jest works together.

Relationship between React-Testing-Library and Jest

What is React-Testing-Library and JEST
React-Testing-Library + Jest
I've commented on the answer which I like

What is Enzyme and Mocha?

Enzyme is replacement for React Testing Library like it's said in the stackoverflow link above. It's not the replacement for Jest.

Mocha is replacement for Jest.

Below combination are possible

  • React Testing Library + Jest -> Our Udemy Course and popular option

  • Enzyme + Jest

  • React Testing Library + Mocha

  • Enzyme + Mocha

How to run tests?

  • To run the create-react-app we use npm start

  • To run the tests, we use npm test

  • Type 'a' to run our tests. This is our watch mode (More on this later)

Basic test details

Assertions

Assertions are the statements that test our expectations against actual results.

Simple assert statement
Jest Vs Jest DOM

How Jest Works?

How Jest Works
Empty test passes

What is Test Driven Development (TDD)?

Writing the tests even before writing the code.

What is TDD
Why TDD

What are the types of Tests?

  • Unit Testing

    • Tests one unit of code at a time. May be a single function or a component without depending on other units

  • Integration Testing

    • Tests how multiple units work together. Testing interaction between different functions/units/components

  • Functional / Behavioural Testing

    • Functional testing means testing the behaviour of a function/software. We might be testing if the software does the right thing with the particular set of data. That might be an integration test as it might have to interact with different units. So the functional test can be an integration test as well

    • The functional test can also be a simple unit test. Let's say on a button click, the div turns red. This might be a simple unit test but still it can be considered as functional test as well as it tests for a particular behaviour of CLICK TURNS RED OR NOT

    • So, the functional test means, not testing the code but testing the behaviour

    • React-Testing-Library encourages functional tests

  • Acceptance/End-to-End(E2E) test

    • This is an End-to-End testing where we need a browser and might also need the server

    • Popular tools for E2E testing are Cypress and Selenium

    • React-Testing-Library doesn't support E2E testing

Unit Testing Vs Functional Testing

TDD (Test-Driven-Development) Vs BDD (Behavioural Driven Development)

If only developer is testing the code while writing the code then its TDD even though we write functional tests for testing behaviour

BDD is when different teams are involved like developer, QA, business and so on to test the software

TDD Vs BDD

Accessibility and Finding Elements with Testing Library

  • Testing library recommends finding elements by accessibility handles like screen readers would be able to find them

What priority to use

Priorities to access the elements (For more details refer the above docs)

  1. Queries Accessible to everyone. Examples:

  • getByRole - All the role definitions can be found here https://www.w3.org/TR/wai-aria-1.1/#role_definitions

    • Most of the HTML / JSX elements has the role by default. For Example, the link is the default role of anchor tag <a> and we don't need to explicitly define it.

    • To define the role explicitly we say role = "". Example, <a role="link"/>

getByRole
  • getByLabelText

2. Semantic Queries

  • getByAltText

  • getByTitle

3. Test IDs

  • getByTestId

Read more about this in the document given below.

Course Plan to learn React-Testing

Course plan

First App - Color Button

Change colour of buttons tests

In the first app, we just test the change of the colour of the button on click. If we click on red button, it will change to blue and vice versa.

Task 1 - Test the background color of button if it's red

Step 1

  • In TDD style, we first write the test before we test the functionality and make it fail

  • First clear the content in App.js and make sure it just returns a simple div

  • Write the test for this first and see it fail and then in step 2, write enough to make the test pass

First test that fails (TDD)
import { render, screen } from '@testing-library/react';
import App from './App';

test('button has correct initial color', () => {
  // first, render the app
  render(<App />)

  // second, find the element. That will be done by global object - screen that has access to virtual dom
  const colorButton = screen.getByRole('button', { name: 'Change To blue' })  // find element with a role of button and text 'Change to blue'

  // third, expect the bgcolor  to red
  expect(colorButton).toHaveStyle({ backgroundColor: 'red' })

})

Step 2

  • Once the test fails, then write enough to make the test pass in App.js file

TDD - make the step pass

Task 2 - Test the button when clicked changes the colour to blue

Steps

  • First write the test and let it fail

  • Now write the function to make the button colour to blue and then see the test pass

Test Before Code

test('button turns blue on click', () => {
  render(<App />)
  const colorButton = screen.getByRole('button', { name: 'Change To Blue' })
  fireEvent.click(colorButton)
  // expect the change of color to blue on click
  expect(colorButton).toHaveStyle({ backgroundColor: 'Blue' })

  //expect the change of text to Change To Red as well after click
  expect(colorButton.textContent).toBe('Change To Red')
  // OR
  // expect(colorButton).toHaveTextContent('Change To Red')
})

// Note : We can write two assert statements sometimes as this is a 
// functional/behavioural testing

Code after Test fails

function App() {
  const [buttonColor, setButtonColor] = React.useState('Red')
  const newButtonColor = buttonColor === 'Red' ? 'Blue' : 'Red'

  return (
    <div>
      <button onClick={() => setButtonColor(newButtonColor)} style={{ backgroundColor: buttonColor }}>Change To {newButtonColor}</button>
    </div>
  );
}

export default App;

Task 3 - Let's add Checkbox to the page. When checkbox is checked, the button should be disabled and when unchecked, the button gets enabled

Steps

  • Write the test for initial condition - to check if the colorButton is enabled and the checkbox is disabled

  • The below test fails as it can't find checkbox

test('initial conditions', () => {
  // the button starts out enabled
  render(<App />)

  const colorButton = screen.getByRole('button', { name: 'Change To Blue' })

  expect(colorButton).toBeEnabled() // checks the button if enabled

  // check that the checkbox is unchecked
  const checkbox = screen.getByRole('checkbox')

  expect(checkbox).not.toBeChecked()
})
  • Let's make the test pass by adding a checkbox into App.js

function App() {
  const [buttonColor, setButtonColor] = React.useState('Red')
  const newButtonColor = buttonColor === 'Red' ? 'Blue' : 'Red'

  return (
    <div>
      <button onClick={() => setButtonColor(newButtonColor)} style={{ backgroundColor: buttonColor }}>Change To {newButtonColor}</button>

      <input type="checkbox" /> // added to make the above test pass 

    </div>
  );
}
  • Let's test for button being disabled after checkbox being checked

test('test for button being disabled after checkbox being checked', () => {

  render(<App />)

  // get the checkbox
  const checkbox = screen.getByRole('checkbox')

  // first check the checkbox through fire-even
  fireEvent.click(checkbox)

  // get the button
  const colorButton = screen.getByRole('button', { name: 'Change To Blue' })

  // then check if button is disabled
  expect(colorButton).toBeDisabled()

})

test('when checkbox is unchecked, the button should be enabled', () => {
  render(<App />)
  const checkbox = screen.getByRole('checkbox')
  const colorButton = screen.getByRole('button', { name: 'Change To Blue' })
  fireEvent.click(checkbox)
  fireEvent.click(checkbox)
  expect(colorButton).toBeEnabled()
})
  • To make the test passed

import logo from './logo.svg';
import React from 'react'
import './App.css';

function App() {
  const [buttonColor, setButtonColor] = React.useState('Red')
  const [buttonEnabled, setButtonEnabled] = React.useState(false)
  const newButtonColor = buttonColor === 'Red' ? 'Blue' : 'Red'

  return (
    <div>
      <button disabled={buttonEnabled} onClick={() => setButtonColor(newButtonColor)} style={{ backgroundColor: buttonColor }}>Change To {newButtonColor}</button>

      <input aria-checked={buttonEnabled}
        type="checkbox" onClick={() => setButtonEnabled(prevState => !prevState)} />

    </div>
  );
}

export default App;

How to give a name to the checkbox?

Name of the checkbox comes from the label assigned to it

Name of the checkbox comes from label

Task 4 - Now let's add functionality where the button colour turns grey

Steps

  • Disable checkbox

  • Check if button is grey

  • Enable checkbox

  • Check if button is red

  • Click the button to change colour to blue

  • Disable checkbox

  • Check if button is grey

  • Enable checkbox

  • Check if button is blue

test('turn the button to gray on disabling it', () => {
  render(<App />)
  const colorButton = screen.getByRole('button', { name: 'Change To Blue' })
  const checkbox = screen.getByRole('checkbox', { name: 'Disable Button' })

  // disable checkbox
  fireEvent.click(checkbox)

  // check if btn is gray
  expect(colorButton).toBeDisabled()
  expect(colorButton).toHaveStyle({ backgroundColor: 'grey' })

  // enable checkbox 
  fireEvent.click(checkbox)

  // check if btn is red
  expect(colorButton).toHaveStyle({ backgroundColor: 'Red' })

  // click btn to change color to blue
  fireEvent.click(colorButton)

  // disable checkbox
  fireEvent.click(checkbox)

  // check if button is gray
  expect(colorButton).toHaveStyle({ backgroundColor: 'grey' })

  // enable checkbox
  fireEvent.click(checkbox)

  // check if btn is blue
  expect(colorButton).toHaveStyle({ backgroundColor: 'Blue' })

})
  • Now after the test fails, implement the passing code and re-test to check if functionality is passed

<button disabled={buttonEnabled} 
  onClick={() => setButtonColor(newButtonColor)} 
  style={{ backgroundColor: buttonEnabled ? 'grey' : buttonColor }}>
  Change To {newButtonColor}
</button>

Unit Tests

Till now we were doing a functional/functionality/behavioural testing on components. Then what is a unit test? Let's say we have a function that is used by one or more components. We might want to test that function with different inputs (different edge cases) and this is called unit test.

Let's take an example here. Let's say our users are bored of red and blue colours and they want Mid night blue and Medium Violet Red.

Here, our function might take different type of inputs like camel case, single word and so on. But at the end, we need a camel case output.

Edge cases

  • Test for - Single word colour like 'red', 'blue' (with no inner capital letters)

  • Test for - Works for one inner capital letter like 'midnightBlue'

  • Test for - Works for multiple inner capital letters like 'mediumVioletRed'

Here's you can see that we need to test a single function with different edge case inputs. In this case we can group these test cases (as it is related to same test with different inputs). For grouping the test cases, we can use describe.

Grouping tests

describe statement is used to group the tests

describe('Spaces before camel-case capital letters', () => {
  test('Works for no inner capital letters', () => {
    expect(replaceCamelWithSpace('red')).toBe('red')
  })

  test('Works for word having one capital letter', () => {
    expect(replaceCamelWithSpace('MidnightBlue')).toBe('Midnight Blue')
  })

  test('Works for word having multiple capital letters', () => {
    expect(replaceCamelWithSpace('MediumVioletRed')).toBe('Medium Violet Red')
  })
})

Function to make this pass

export function replaceCamelWithSpace(colorName) {
  // replacing capitals with spaces if the capital letter appears in the middle
  return colorName.replace(/\B([A-Z])\B/g, ' $1')
}

What all you learned?

Task
How to do
  • FIRST APP - COLOUR BUTTON

    How to test text content on screen

  • How to check if button is enabled and disabled

  • How to check if checkbox is enabled and disabled

SECOND APP

Last updated

Was this helpful?