Testing React with Jest and RTL

Jest and Enzyme

  • Jest is a fully feature testing framework not just for React
  • Enzyme is a library that makes testing React components specifically easier
  • If you are using React in your application, it might make sense to use Enzyme and Jest together to automatically test your UI
  • Many people use Jest as a test runner and assertion library, then use Enzyme to build the tests for their UI

Jest compares snapshots

  • Snapshot is a saved fragment of HTML generated by your application
  • when Jest run tests, it compares the output of rendering a component with the saved snapshot HTML, it they are the same, then the tests passes.

Issues with snapshots

  • if your component has many states, you have to create a snapshot for each state.

Enzyme

  • Enzyme renders the component in memory, then provides a series of APIs to examine the component’s properties

Shallow rendering

  • only render that component itself, Enzyme doesn’t render any of the children of that component
  • then you could use functions like find() or findWhere() to find the element within the component

Full rendering

  • useful for integration testing, where you need to test multiple components and how they work together

Introduction

Create react app

  • npm package
  • Creates react application with
    • configuration
    • webpack and babel
      • webpack will bundle all your code in one file
      • babel will translate react to the code that our browser can understand
    • web server
      • to run our app on localhost
    • testing library

npx

  • downloads the latest version of create-react-app templates every time
  • not dependent on when you last installed create-react-app
    • it is never installed on your laptop

Breaking down syntax

  • render
    • create virtual DOM for argument JSX
  • screen
    • global object to access virtual DOM
    • screen.getByText() to find element by display text
  • expect
    • assertion, causes test to succeed to fail

Jest and React Testing Library

  • React Testing Library helps with
    • rendering components into virtual DOM
    • searching virtual DOM
    • interacting with virtual DOM
  • needs a test runner
    • find tests, run them, make assertions
  • Jest is the test runner
    • is recommended by Testing Library
    • comes with create-react-app
  • npm test runs an npm script that runs Jest in watch mode

Jest Watch mode

  • watch for changes in files since last commit
  • only run tests related to these files
  • no changes? no tests.

How does Jest work

  • global test method has two arguments
    • string description
    • test function
  • test fails if error is thrown when running function
    • assertions throw errors when expectation fails
  • no error -> tests pass
    • empty test passes

TDD

  • write tests before writing code
    • then write code according to spec set by tests
  • red green testing
    • tests fail before code is written

React Testing Library

  • creates virtual DOM for testing
    • and utilities for interacting with DOM
  • allows testing without a browser

Types of tests

  • Unit test
    • tests one unit of code in isolation
    • mock dependencies, test internals
    • easy to pinpoint failures but further from how users interact with software
    • more likely to break with refactoring
  • Integration tests
    • how multiple units work together
  • functional tests
    • tests a particular function of software
    • you are not testing your code, you are testing a behavior
    • close to how users interact with software
    • more difficult to debug failing tests (tests are not tightly coupled with the code)
    • e.g. enter data in form and click submit
  • acceptance E2E tests
    • use actual browser and server (Cypress, Selenium)

BDD

  • Behavior driven development
  • testing library encourages testing behavior over implementation
  • but we still call it TDD, why?
  • because BDD is very explicitly defined
    • involves collaboration between lots of roles
    • developers, QA, business partners, etc…
    • defines process for different groups to interact

Unit testing functions

  • functions separate from components
    • used by several components
    • complex logic
  • unit test if
    • complex logic difficult to test via functional tests
    • too many edge cases

Review

  • Test interactivity using fireEvent
    • userEvent is more popular than fireEvent
  • jest DOM assertions
    • toBeEnabled()
    • toBeDisabled()
    • toBeChecked()
  • getByRole option name
  • jest describe to group tests

ESLint and Prettier

ESLint

  • Linter
    • analyzes static text and marks syntax that breaks rules
  • Static
    • analyze code as written, not what happens when code is run
  • Popular linter for JavaScript
  • linting keeps code style consistent
    • especially for multi engineer projects
  • also catches errors in code
    • using variable before defining
    • importing from non-existing file
    • etc…

Linting vs Formatting

  • Formatters (like prettier) automatically format code
  • linters address format and code style
    • enforce best practices

Sundaes on Demand: Form review and popover

  • more complex user interactions
    • multiple form entry, moving through order phases
  • mouseover popup
    • test that element disappears from DOM
  • simulating server response
    • mock service worker (without the need of a real backend server)
    • mock responses from server
  • async app updates
    • tell assertion to wait until DOM changes
  • global state via context

Code organization

  • organize components by pages
    • test directory for each page
    • Jest will find and run any files that end in .test.js

screen Query methods

  • get: expect element to be in DOM
  • query: expect element NOT to be in DOM
  • find: expect element to appear async

Check that tests can fail

  • sometimes tests pass because of async and assertions don’t have a chance to run before the tests end.
  • so it is a good habit to check that the tests can fail as well as pass

wait for element to disappear

1
2
3
4
5
// popover disappears when we mouse out
userEvent.unhover(termsAndConditions);
await waitForElementToBeRemoved(() =>
screen.queryByText(/no ice cream will actually be delivered/i)
);
  • sometimes the disappearance of elements is happening asynchronously, so we need to wait for it before doing assertions

Review

  • userEvent.hover and userEvent.unhover
  • queryByText
  • async waitForElementToBeRemoved
  • test not wrapped in act(...) warning
    • determine how component is getting updated async and account for in tests