Giter Site home page Giter Site logo

jakobsandberg / react-testing-library Goto Github PK

View Code? Open in Web Editor NEW

This project forked from testing-library/react-testing-library

0.0 1.0 0.0 522 KB

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.

Home Page: http://npm.im/react-testing-library

License: MIT License

JavaScript 100.00%

react-testing-library's Introduction

react-testing-library

goat

Simple and complete React DOM testing utilities that encourage good testing practices.


Build Status Code Coverage version downloads MIT License

All Contributors PRs Welcome Code of Conduct Join the community on Spectrum

Watch on GitHub Star on GitHub Tweet

The problem

You want to write maintainable tests for your React components. As a part of this goal, you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended. As part of this, you want your testbase to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.

This solution

The react-testing-library is a very light-weight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils, in a way that encourages better testing practices. Its primary guiding principle is:

The more your tests resemble the way your software is used, the more confidence they can give you.

So rather than dealing with instances of rendered react components, your tests will work with actual DOM nodes. The utilities this library provides facilitate querying the DOM in the same way the user would. Finding for elements by their label text (just like a user would), finding links and buttons from their text (like a user would). It also exposes a recommended way to find elements by a data-testid as an "escape hatch" for elements where the text content and label do not make sense or is not practical.

This library encourages your applications to be more accessible and allows you to get your tests closer to using your components the way a user will, which allows your tests to give you more confidence that your application will work when a real user uses it.

This library is a replacement for enzyme. While you can follow these guidelines using enzyme itself, enforcing this is harder because of all the extra utilities that enzyme provides (utilities which facilitate testing implementation details). Read more about this in the FAQ below.

What this library is not:

  1. A test runner or framework
  2. Specific to a testing framework (though we recommend Jest as our preference, the library works with any framework. See Using Without Jest)

NOTE: This library is built on top of dom-testing-library which is where most of the logic behind the queries is.

What is react-testing-library?

Have a look at the video below for an explanation.

what is react testing library

Example

// __tests__/fetch.js
import React from 'react'
import {render, fireEvent, cleanup, waitForElement} from 'react-testing-library'
// this adds custom jest matchers from jest-dom
import 'jest-dom/extend-expect'

// the mock lives in a __mocks__ directory
// to know more about manual mocks, access: https://jestjs.io/docs/en/manual-mocks
import axiosMock from 'axios'
import Fetch from '../fetch' // see the tests for a full implementation

// automatically unmount and cleanup DOM after the test is finished.
afterEach(cleanup)

test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => {
  // Arrange
  axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}})
  const url = '/greeting'
  const {getByText, getByTestId, container, asFragment} = render(
    <Fetch url={url} />,
  )

  // Act
  fireEvent.click(getByText('Load Greeting'))

  // Let's wait until our mocked `get` request promise resolves and
  // the component calls setState and re-renders.
  // getByTestId throws an error if it cannot find an element with the given ID
  // and waitForElement will wait until the callback doesn't throw an error
  const greetingTextNode = await waitForElement(() =>
    getByTestId('greeting-text'),
  )

  // Assert
  expect(axiosMock.get).toHaveBeenCalledTimes(1)
  expect(axiosMock.get).toHaveBeenCalledWith(url)
  expect(getByTestId('greeting-text')).toHaveTextContent('hello there')
  expect(getByTestId('ok-button')).toHaveAttribute('disabled')
  // snapshots work great with regular DOM nodes!
  expect(container.firstChild).toMatchSnapshot()
  // you can also use get a `DocumentFragment`, which is useful if you want to compare nodes across render
  expect(asFragment()).toMatchSnapshot()
})

Table of Contents

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:

npm install --save-dev react-testing-library

This library has a peerDependencies listing for react-dom.

You may also be interested in installing jest-dom so you can use the custom jest matchers.

Setup

react-testing-library does not require any configuration to be used (as demonstrated in the example above). However, there are some things you can do when configuring your testing framework to reduce some boilerplate. In these docs we'll demonstrate configuring Jest, but you should be able to do similar things with any testing framework (react-testing-library does not require that you use Jest).

Global Config

There are several options you can add to your global test config that simplify the setup and teardown of tests in individual files. For example, you can ensure cleanup is called after each test and import additional assertions.

To do this with Jest, you can add the setupTestFrameworkScriptFile option to your Jest config. The setup file can be anywhere, for example jest.setup.js or ./utils/setupTests.js.

If you are using the default setup from create-react-app, this option is set to src/setupTests.js. You should create this file if it doesn't exist and put the setup code there.

// jest.config.js
module.exports = {
  setupTestFrameworkScriptFile: require.resolve('./jest.setup.js'),
  // ... other options ...
}
// jest.setup.js

// add some helpful assertions
import 'jest-dom/extend-expect'

// this is basically: afterEach(cleanup)
import 'react-testing-library/cleanup-after-each'

Custom Render

It's often useful to define a custom render method that includes things like global context providers, data stores, etc. To make this available globally, one approach is to define a utility file that re-exports everything from react-testing-library. You can replace react-testing-library with this file in all your imports.

// my-component.test.js
- import { render, fireEvent } from 'react-testing-library';
+ import { render, fireEvent } from '../test-utils';
// test-utils.js
import {render} from 'react-testing-library'
import {ThemeProvider} from 'my-ui-lib'
import {TranslationProvider} from 'my-i18n-lib'
import defaultStrings from 'i18n/en-x-default'

const customRender = (node, options) => {
  return render(
    <ThemeProvider theme="light">
      <TranslationProvider messages={defaultStrings}>
        {node}
      </TranslationProvider>
    </ThemeProvider>,
    options,
  )
}

// re-export everything
export * from 'react-testing-library'

// override render method
export {customRender as render}

To make this file accessible without using relative imports, add the folder containing the file to the Jest moduleDirectories option. Note: this will make all the .js files in that directory importable without ../.

// my-component.test.js
- import { render, fireEvent } from '../test-utils';
+ import { render, fireEvent } from 'test-utils';
// jest.config.js
module.exports = {
  moduleDirectories: [
    'node_modules',
+   // add the directory with the test-utils.js file, for example:
+   'utils', // a utility folder
+    __dirname, // the root directory
  ],
  // ... other options ...
}

If your project is based on top of Create React App, to make the file accessible without using relative imports, you just need to create a .env file in the root of your project with the following configuration:

// Create React App project structure

$ app
.
β”œβ”€β”€ .env
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ utils
β”‚   β”‚  └── test-utils.js
β”‚
// .env

// example if your utils folder is inside the /src directory.
NODE_PATH=src/utils

There is the case when you want to wrap your components in a Provider, this might cause conflicts when rerendered. To achieve this, we suggest the rerender should be implemented the same way custom queries, by changing the return value of the customRender.

// test-utils.js

const customRender = (ui, options) => {
  const rendered = render(<div>{ui}</div>, options)
  return {
    ...rendered,
    rerender: newUi =>
      customRender(newUi, {
        container: rendered.container,
        baseElement: rendered.baseElement,
      }),
  }
}

Export Issue with Babel Versions Lower Than 7

Babel versions lower than 7 throw an error when trying to override the named export in the example above. (See #169.)

Workaround

You can use CommonJS modules instead of ES modules, which should work in Node:

// test-utils.js
const rtl = require('react-testing-library')

const customRender = (node, options) => {
  return rtl.render(<Something>{node}</Something>)
}

module.exports = {
  ...rtl,
  render: customRender,
}

Usage

render

Defined as:

function render(
  ui: React.ReactElement<any>,
  options?: {
    /* You won't often use this, expand below for docs on options */
  },
): RenderResult

Render into a container which is appended to document.body. It should be used with cleanup:

import {render} from 'react-testing-library'

render(<div />)
Expand to see documentation on the options

You wont often need to specify options, but if you ever do, here are the available options which you could provide as a second argument to render.

container: By default, react-testing-library will create a div and append that div to the document.body and this is where your react component will be rendered. If you provide your own HTMLElement container via this option, it will not be appended to the document.body automatically.

For Example: If you are unit testing a tablebody element, it cannot be a child of a div. In this case, you can specify a table as the render container.

const table = document.createElement('table')

const {container} = render(<TableBody {...props} />, {
  container: document.body.appendChild(table),
})

baseElement: If the container is specified, then this defaults to that, otherwise this defaults to document.documentElement. This is used as the base element for the queries as well as what is printed when you use debug().

hydrate: If hydrate is set to true, then it will render with ReactDOM.hydrate. This may be useful if you are using server-side rendering and use ReactDOM.hydrate to mount your components.

In the example above, the render method returns an object that has a few properties:

container

The containing DOM node of your rendered React Element (rendered using ReactDOM.render). It's a div. This is a regular DOM node, so you can call container.querySelector etc. to inspect the children.

Tip: To get the root element of your rendered element, use container.firstChild.

NOTE: When that root element is a React Fragment, container.firstChild will only get the first child of that Fragment, not the Fragment itself.

🚨 If you find yourself using container to query for rendered elements then you should reconsider! The other queries are designed to be more resiliant to changes that will be made to the component you're testing. Avoid using container to query for elements!

baseElement

The containing DOM node where your React Element is rendered in the container. If you don't specify the baseElement in the options of render, it will default to document.body.

This is useful when the component you want to test renders something outside the container div, e.g. when you want to snapshot test your portal component which renders it's HTML directly in the body.

Note: the queries returned by the render looks into baseElement, so you can use queries to test your portal component without the baseElement.

debug

This method is a shortcut for console.log(prettyDOM(baseElement)).

import React from 'react'
import {render} from 'react-testing-library'

const HelloWorld = () => <h1>Hello World</h1>
const {debug} = render(<HelloWorld />)
debug()
// <div>
//   <h1>Hello World</h1>
// </div>
// you can also pass an element: debug(getByTestId('messages'))

This is a simple wrapper around prettyDOM which is also exposed and comes from dom-testing-library.

rerender

It'd probably be better if you test the component that's doing the prop updating to ensure that the props are being updated correctly (see the Guiding Principles section). That said, if you'd prefer to update the props of a rendered component in your test, this function can be used to update props of the rendered component.

import {render} from 'react-testing-library'

const {rerender} = render(<NumberDisplay number={1} />)

// re-render the same component with different props
rerender(<NumberDisplay number={2} />)

Open the tests for a full example of this.

unmount

This will cause the rendered component to be unmounted. This is useful for testing what happens when your component is removed from the page (like testing that you don't leave event handlers hanging around causing memory leaks).

This method is a pretty small abstraction over ReactDOM.unmountComponentAtNode

import {render} from 'react-testing-library'

const {container, unmount} = render(<Login />)
unmount()
// your component has been unmounted and now: container.innerHTML === ''

getByLabelText(text: TextMatch, options): HTMLElement

Options: {selector = '*', exact = true, collapseWhitespace = true, trim = true}

This will search for the label that matches the given TextMatch, then find the element associated with that label.

import {render} from 'react-testing-library'

const {getByLabelText} = render(<Login />)
const inputNode = getByLabelText('Username')

// this would find the input node for the following DOM structures:
// The "for" attribute (NOTE: in JSX with React you'll write "htmlFor" rather than "for")
// <label for="username-input">Username</label>
// <input id="username-input" />
//
// The aria-labelledby attribute
// <label id="username-label">Username</label>
// <input aria-labelledby="username-label" />
//
// Wrapper labels
// <label>Username <input /></label>
//
// It will NOT find the input node for this:
// <label><span>Username</span> <input /></label>
//
// For this case, you can provide a `selector` in the options:
const inputNode = getByLabelText('username', {selector: 'input'})
// and that would work
// Note that <input aria-label="username" /> will also work, but take
// care because this is not a label that users can see on the page. So
// the purpose of your input should be obvious for those users.

Note: This method will throw an error if it cannot find the node. If you don't want this behavior (for example you wish to assert that it doesn't exist), then use queryByLabelText instead.

getByPlaceholderText(text: TextMatch, options): HTMLElement

Options: {exact = true, collapseWhitespace = true, trim = true}

This will search for all elements with a placeholder attribute and find one that matches the given TextMatch.

import {render} from 'react-testing-library'

const {getByPlaceholderText} = render(<input placeholder="Username" />)
const inputNode = getByPlaceholderText('Username')

NOTE: a placeholder is not a good substitute for a label so you should generally use getByLabelText instead.

getByText(text: TextMatch, options): HTMLElement

Options: {selector = '*', exact = true, collapseWhitespace = true, trim = true, ignore = 'script, style'}

This will search for all elements that have a text node with textContent matching the given TextMatch.

import {render} from 'react-testing-library'

const {getByText} = render(<a href="/about">About ℹ️</a>)
const aboutAnchorNode = getByText('about')

getByAltText(text: TextMatch, options): HTMLElement

Options: {exact = true, collapseWhitespace = true, trim = true}

This will return the element (normally an <img>) that has the given alt text. Note that it only supports elements which accept an alt attribute: <img>, <input>, and <area> (intentionally excluding <applet> as it's deprecated).

import {render} from 'react-testing-library'

const {getByAltText} = render(
  <img alt="Incredibles 2 Poster" src="/incredibles-2.png" />,
)
const incrediblesPosterImg = getByAltText(/incredibles.*poster$/i)

getByTestId(text: TextMatch, options): HTMLElement

Options: {exact = true, collapseWhitespace = true, trim = true}

A shortcut to container.querySelector(`[data-testid="${yourId}"]`) (and it also accepts a TextMatch).

import {render} from 'react-testing-library'

const {getByTestId} = render(<input data-testid="username-input" />)
const usernameInputElement = getByTestId('username-input')

In the spirit of the guiding principles, it is recommended to use this only after getByLabel, getByPlaceholderText or getByText don't work for your use case. Using data-testid attributes do not resemble how your software is used and should be avoided if possible. That said, they are way better than querying based on DOM structure. Learn more about data-testids from the blog post "Making your UI tests resilient to change"

What if my project already uses data-test-id or another attribute? Do I have to migrate to data-testid?

If you're starting out with a new codebase, it's recommended that you stick with data-testid, following the precedent set by React Native Web, but if you already have a codebase that uses a different attribute for this purpose, you can use the configure function of dom-testing-library to change the attribute that is used. This requires dom-testing-library version 3.13:

import {configure} from 'dom-testing-library'
configure({testIdAttribute: 'data-test-id'})

asFragment(): DocumentFragment

Returns a DocumentFragment of your rendered component. This can be useful if you need to avoid live bindings and see how your component reacts to events.

import {render, fireEvent} from 'react-testing-library'

class TestComponent extends React.Component {
  constructor() {
    super()
    this.state = {count: 0}
  }

  render() {
    const {count} = this.state

    return (
      <button onClick={() => this.setState({count: count + 1})}>
        Click to increase: {count}
      </button>
    )
  }
}

const {getByText, asFragment} = render(<TestComponent />)
const firstRender = asFragment()

fireEvent.click(getByText(/Click to increase/))

// This will snapshot only the difference between the first render, and the
// state of the DOM after the click event.
// See https://github.com/jest-community/snapshot-diff
expect(firstRender).toMatchDiffSnapshot(asFragment())

cleanup

Unmounts React trees that were mounted with render.

import {cleanup, render} from 'react-testing-library'

afterEach(cleanup) // <-- add this

test('renders into document', () => {
  render(<div />)
  // ...
})

// ... more tests ...

Failing to call cleanup when you've called render could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).

If you don't want to add this to every single test file then we recommend that you configure your test framework to run a file before your tests which does this automatically. See the setup section for guidance on how to set up your framework.

flushEffects (experimental)

This experimental API is intended to be used to force React's useEffect hook to run synchronously.

dom-testing-library APIs

react-testing-library is built on top of dom-testing-library and re-exports everything from dom-testing-library. Some notable included exports:

fireEvent(node: HTMLElement, event: Event)

Fire DOM events.

React attaches an event handler on the document and handles some DOM events via event delegation (events bubbling up from a target to an ancestor). Because of this, your node must be in the document.body for fireEvent to work with React. This is why render appends your container to document.body. This is an alternative to simulating Synthetic React Events via Simulate. The benefit of using fireEvent over Simulate is that you are testing real DOM events instead of Synthetic Events. This aligns better with the Guiding Principles. (Also Dan Abramov told me to stop use Simulate).

NOTE: If you don't like having to use cleanup (which we have to do because we render into document.body) to get fireEvent working, then feel free to try to chip into making it possible for React to attach event handlers to the rendered node rather than the document. Learn more here: facebook/react#2043

import {render, cleanup, fireEvent} from 'react-testing-library'

// don't forget to clean up the document.body
afterEach(cleanup)

test('clicks submit button', () => {
  const handleClick = jest.fn()
  const {getByText} = render(<button onClick={handleClick}>Submit</button>)

  fireEvent.click(getByText('Submit'))
  expect(handleClick).toHaveBeenCalledTimes(1)
})

fireEvent[eventName](node: HTMLElement, eventProperties: Object)

Convenience methods for firing DOM events. Check out dom-testing-library/src/events.js for a full list as well as default eventProperties.

import {render, fireEvent} from 'react-testing-library'

const {getByText} = render(<Form />)

// similar to the above example
// click will bubble for React to see it
const rightClick = {button: 2}
fireEvent.click(getByText('Submit'), rightClick)
// default `button` property for click events is set to `0` which is a left click.

If you want to trigger the onChange handler of a controlled component with a different event.target.value, sending value through eventProperties won't work like it does with Simulate. You need to use fireEvent to fire a change DOM event with value property set on target

import {render, fireEvent} from 'react-testing-library'

const {getByLabelText} = render(<Form />)

const comment = getByLabelText('Comment')
fireEvent.change(comment, {
  target: {value: 'Great advice, I love your posts!'},
})

Note that if you want to trigger onChange handler on a checkbox, you should fire a click event instead of change.

import {render, fireEvent} from 'react-testing-library'

const {getByLabelText} = render(<Checkbox />)

fireEvent.click(getByLabelText('Checkbox'))

waitForElement

Read full docs from dom-testing-library

import {render, waitForElement} from 'react-testing-library'

test('waiting for an element', async () => {
  const {getByText} = render(<SearchForm />)

  await waitForElement(() => getByText('Search'))
})

wait

Read full docs from dom-testing-library

It's recommended to prefer waitForElement, but this can be helpful on occasion

import 'jest-dom/extend-expect'
import {render, wait} from 'react-testing-library'

test('can fill in the form after loaded', async () => {
  const {queryByText, getByLabelText} = render(<Login />)

  // wait until the callback does not throw an error. In this case, that means
  // it'll wait until the element with the text that says "loading..." is gone.
  await wait(() =>
    expect(queryByText(/loading\.\.\./i)).not.toBeInTheDocument(),
  )
  getByLabelText('username').value = 'chucknorris'
  // continue doing stuff
})

within

Read full docs from dom-testing-library

The queries returned from render are scoped to the entire page. Sometimes, there is no guarantee that the text, placeholder, or label you want to query is unique on the page. So you might want to explicitly tell react-render-dom to get an element only within a particular section of the page, within is a helper function for this case.

Example: To get the text 'hello' only within a section called 'messages', you could do:

import {render, within} from 'react-testing-library'

// ...

const {getByTestId} = render(/* stuff */)
const messagesSection = getByTestId('messages')
const hello = within(messagesSection).getByText('hello')

TextMatch

Several APIs accept a TextMatch which can be a string, regex or a function which returns true for a match and false for a mismatch.

See dom-testing-library#textmatch for options.

Examples:

import {render, getByText} from 'react-testing-library'

const {container} = render(<div>Hello World</div>)

// WILL find the div:

// Matching a string:
getByText(container, 'Hello World') // full string match
getByText(container, 'llo Worl', {exact: false}) // substring match
getByText(container, 'hello world', {exact: false}) // ignore case

// Matching a regex:
getByText(container, /World/) // substring match
getByText(container, /world/i) // substring match, ignore case
getByText(container, /^hello world$/i) // full string match, ignore case
getByText(container, /Hello W?oRlD/i) // advanced regex

// Matching with a custom function:
getByText(container, (content, element) => content.startsWith('Hello'))

// WILL NOT find the div:

getByText(container, 'Goodbye World') // full string does not match
getByText(container, /hello world/) // case-sensitive regex with different case
// function looking for a span when it's actually a div:
getByText(container, (content, element) => {
  return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})

query APIs

Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the DOM, then you can use the query API instead:

import {render} from 'react-testing-library'

const {queryByText} = render(<Form />)
const submitButton = queryByText('submit')
expect(submitButton).toBeNull() // it doesn't exist

queryAll and getAll APIs

Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0.

import {render} from 'react-testing-library'

const {queryAllByText} = render(<Forms />)
const submitButtons = queryAllByText('submit')
expect(submitButtons).toHaveLength(3) // expect 3 elements
expect(submitButtons[0]).toBeInTheDocument()

Examples

We're in the process of moving examples to react-testing-library-examples.

You'll find examples of testing with different libraries in the examples directory. Some included are:

You can also find react-testing-library examples at react-testing-examples.com.

Learning Material

Feel free to contribute more!

FAQ

How do I test input onChange handlers?

TL;DR: Go to the on-change.js example

In summary:

import React from 'react'
import 'react-testing-library/cleanup-after-each'
import {render, fireEvent} from 'react-testing-library'

test('change values via the fireEvent.change method', () => {
  const handleChange = jest.fn()
  const {container} = render(<input type="text" onChange={handleChange} />)
  const input = container.firstChild
  fireEvent.change(input, {target: {value: 'a'}})
  expect(handleChange).toHaveBeenCalledTimes(1)
  expect(input.value).toBe('a')
})

test('checkboxes (and radios) must use fireEvent.click', () => {
  const handleChange = jest.fn()
  const {container} = render(<input type="checkbox" onChange={handleChange} />)
  const checkbox = container.firstChild
  fireEvent.click(checkbox)
  expect(handleChange).toHaveBeenCalledTimes(1)
  expect(checkbox.checked).toBe(true)
})

If you've used enzyme or React's TestUtils, you may be accustomed to changing inputs like so:

input.value = 'a'
Simulate.change(input)

We can't do this with react-testing-library because React actually keeps track of any time you assign the value property on an input and so when you fire the change event, React thinks that the value hasn't actually been changed.

This works for Simulate because they use internal APIs to fire special simulated events. With react-testing-library, we try to avoid implementation details to make your tests more resiliant.

So we have it worked out for the change event handler to set the property for you in a way that's not trackable by React. This is why you must pass the value as part of the change method call.

Which get method should I use?

Based on the Guiding Principles, your test should resemble how your code (component, page, etc.) is used as much as possible. With this in mind, we recommend this order of priority:

  1. getByLabelText: Only really good for form fields, but this is the number 1 method a user finds those elements, so it should be your top preference.
  2. getByPlaceholderText: A placeholder is not a substitute for a label. But if that's all you have, then it's better than alternatives.
  3. getByText: Not useful for forms, but this is the number 1 method a user finds other elements (like buttons to click), so it should be your top preference for non-form elements.
  4. getByAltText: If your element is one which supports alt text (img, area, and input), then you can use this to find that element.
  5. getByTestId: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense (the text is dynamic).

Other than that, you can also use the container to query the rendered component as well (using the regular querySelector API).

Can I write unit tests with this library?

Definitely yes! You can write unit and integration tests with this library. See below for more on how to mock dependencies (because this library intentionally does NOT support shallow rendering) if you want to unit test a high level component. The tests in this project show several examples of unit testing with this library.

As you write your tests, keep in mind:

The more your tests resemble the way your software is used, the more confidence they can give you. - 17 Feb 2018

What if my app is localized and I don't have access to the text in test?

This is fairly common. Our first bit of advice is to try to get the default text used in your tests. That will make everything much easier (more than just using this utility). If that's not possible, then you're probably best to just stick with data-testids (which is not bad anyway).

If I can't use shallow rendering, how do I mock out components in tests?

In general, you should avoid mocking out components (see the Guiding Principles section). However if you need to, then it's pretty trivial using Jest's mocking feature. One case that I've found mocking to be especially useful is for animation libraries. I don't want my tests to wait for animations to end.

jest.mock('react-transition-group', () => {
  const FakeTransition = jest.fn(({children}) => children)
  const FakeCSSTransition = jest.fn(props =>
    props.in ? <FakeTransition>{props.children}</FakeTransition> : null,
  )
  return {CSSTransition: FakeCSSTransition, Transition: FakeTransition}
})

test('you can mock things with jest.mock', () => {
  const {getByTestId, queryByTestId} = render(
    <HiddenMessage initialShow={true} />,
  )
  expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists
  // hide the message
  fireEvent.click(getByTestId('toggle-message'))
  // in the real world, the CSSTransition component would take some time
  // before finishing the animation which would actually hide the message.
  // So we've mocked it out for our tests to make it happen instantly
  expect(queryByTestId('hidden-message')).toBeNull() // we just care it doesn't exist
})

Note that because they're Jest mock functions (jest.fn()), you could also make assertions on those as well if you wanted.

Open full test for the full example.

This looks like more work that shallow rendering (and it is), but it gives you more confidence so long as your mock resembles the thing you're mocking closely enough.

If you want to make things more like shallow rendering, then you could do something more like this.

Learn more about how Jest mocks work from my blog post: "But really, what is a JavaScript mock?"

What if I want to verify that an element does NOT exist?

You typically will get access to rendered elements using the getByTestId utility. However, that function will throw an error if the element isn't found. If you want to specifically test for the absence of an element, then you should use the queryByTestId utility which will return the element if found or null if not.

expect(queryByTestId('thing-that-does-not-exist')).toBeNull()
I really don't like data-testids, but none of the other queries make sense. Do I have to use a data-testid?

Definitely not. That said, a common reason people don't like the data-testid attribute is they're concerned about shipping that to production. I'd suggest that you probably want some simple E2E tests that run in production on occasion to make certain that things are working smoothly. In that case the data-testid attributes will be very useful. Even if you don't run these in production, you may want to run some E2E tests that run on the same code you're about to ship to production. In that case, the data-testid attributes will be valuable there as well.

All that said, if you really don't want to ship data-testid attributes, then you can use this simple babel plugin to remove them.

If you don't want to use them at all, then you can simply use regular DOM methods and properties to query elements off your container.

const firstLiInDiv = container.querySelector('div li')
const allLisInDiv = container.querySelectorAll('div li')
const rootElement = container.firstChild
What if I’m iterating over a list of items that I want to put the data-testid="item" attribute on. How do I distinguish them from each other?

You can make your selector just choose the one you want by including :nth-child in the selector.

const thirdLiInUl = container.querySelector('ul > li:nth-child(3)')

Or you could include the index or an ID in your attribute:

<li data-testid={`item-${item.id}`}>{item.text}</li>

And then you could use the getByTestId utility:

const items = [
  /* your items */
]
const {getByTestId} = render(/* your component with the items */)
const thirdItem = getByTestId(`item-${items[2].id}`)
What about enzyme is "bloated with complexity and features" and "encourage poor testing practices"?

Most of the damaging features have to do with encouraging testing implementation details. Primarily, these are shallow rendering, APIs which allow selecting rendered elements by component constructors, and APIs which allow you to get and interact with component instances (and their state/properties) (most of enzyme's wrapper APIs allow this).

The guiding principle for this library is:

The more your tests resemble the way your software is used, the more confidence they can give you. - 17 Feb 2018

Because users can't directly interact with your app's component instances, assert on their internal state or what components they render, or call their internal methods, doing those things in your tests reduce the confidence they're able to give you.

That's not to say that there's never a use case for doing those things, so they should be possible to accomplish, just not the default and natural way to test react components.

Why isn't snapshot diffing working?

If you use the snapshot-diff library to save snapshot diffs, it won't work out of the box because this library uses the DOM which is mutable. Changes don't return new objects so snapshot-diff will think it's the same object and avoid diffing it.

Luckily there's an easy way to make it work: clone the DOM when passing it into snapshot-diff. It looks like this:

const firstVersion = container.cloneNode(true)
// Do some changes
snapshotDiff(firstVersion, container.cloneNode(true))
Does this library work with React Native?

This is still quite experimental - please contribute with your own results/findings!

The short answer is yes, but with a few caveats. It's possible to replicate a lot of DOM functionality with react-native-web, allowing you to use the query APIs like getByText. You can then add a press event to fireEvent that simulates a mouseDown immediately followed by a mouseUp, and call this with Touchable* components.

One thing this approach does not support is any kind of native module functionality (like native navigation modules). The way around this is to design your components so that as much of the functionality you need tested is encapsulated outside of any native module functionality.

For a barebones example of testing a React Native component, see here.

There is also a sibling project called react-native-testing-library which aims to test React Native apps without mentioned tradeoffs, having the API inspired by and mostly compatible with this library.

Other Solutions

In preparing this project, I tweeted about it and Sune Simonsen took up the challenge. We had different ideas of what to include in the library, so I decided to create this one instead.

Guiding Principles

The more your tests resemble the way your software is used, the more confidence they can give you.

We try to only expose methods and utilities that encourage you to write tests that closely resemble how your react components are used.

Utilities are included in this project based on the following guiding principles:

  1. If it relates to rendering components, it deals with DOM nodes rather than component instances, nor should it encourage dealing with component instances.
  2. It should be generally useful for testing individual React components or full React applications. While this library is focused on react-dom, utilities could be included even if they don't directly relate to react-dom.
  3. Utility implementations and APIs should be simple and flexible.

At the end of the day, what we want is for this library to be pretty light-weight, simple, and understandable.

Contributors

Thanks goes to these people (emoji key):


Kent C. Dodds

πŸ’» πŸ“– πŸš‡ ⚠️

Ryan Castner

πŸ“–

Daniel Sandiego

πŸ’»

PaweΕ‚ MikoΕ‚ajczyk

πŸ’»

Alejandro ÑÑñez Ortiz

πŸ“–

Matt Parrish

πŸ› πŸ’» πŸ“– ⚠️

Justin Hall

πŸ“¦

Anto Aravinth

πŸ’» ⚠️ πŸ“–

Jonah Moses

πŸ“–

Łukasz Gandecki

πŸ’» ⚠️ πŸ“–

Ivan Babak

πŸ› πŸ€”

Jesse Day

πŸ’»

Ernesto GarcΓ­a

πŸ’¬ πŸ’» πŸ“–

Josef Maxx Blake

πŸ’» πŸ“– ⚠️

Michal Baranowski

πŸ“ βœ…

Arthur Puthin

πŸ“–

Thomas Chia

πŸ’» πŸ“–

Thiago Galvani

πŸ“–

Christian

⚠️

Alex Krolick

πŸ’¬ πŸ“– πŸ’‘ πŸ€”

Johann Hubert Sonntagbauer

πŸ’» πŸ“– ⚠️

Maddi Joyce

πŸ’»

Ryan Vice

πŸ“–

Ian Wilson

πŸ“ βœ…

Daniel

πŸ› πŸ’»

Giorgio Polvara

πŸ› πŸ€”

John Gozde

πŸ’»

Sam Horton

πŸ“– πŸ’‘ πŸ€”

Richard Kotze (mobile)

πŸ“–

Brahian E. Soto Mercedes

πŸ“–

Benoit de La Forest

πŸ“–

Salah

πŸ’» ⚠️

Adam Gordon

πŸ› πŸ’»

Matija Marohnić

πŸ“–

Justice Mba

πŸ“–

Mark Pollmann

πŸ“–

Ehtesham Kafeel

πŸ’» πŸ“–

Julio PavΓ³n

πŸ’»

Duncan L

πŸ“– πŸ’‘

Tiago Almeida

πŸ“–

Robert Smith

πŸ›

Zach Green

πŸ“–

dadamssg

πŸ“–

Yazan Aabed

πŸ“

Tim

πŸ› πŸ’» πŸ“– ⚠️

Divyanshu Maithani

βœ… πŸ“Ή

Deepak Grover

βœ… πŸ“Ή

Eyal Cohen

πŸ“–

Peter Makowski

πŸ“–

Michiel Nuyts

πŸ“–

Joe Ng'ethe

πŸ’» πŸ“–

Kate

πŸ“–

Sean

πŸ“–

James Long

πŸ€” πŸ“¦

Herb Hagely

πŸ’‘

Alex Wendte

πŸ’‘

Monica Powell

πŸ“–

Vitaly Sivkov

πŸ’»

Weyert de Boer

πŸ€” πŸ‘€

EstebanMarin

πŸ“–

Victor Martins

πŸ“–

Royston Shufflebotham

πŸ› πŸ“– πŸ’‘

chrbala

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

Issues

Looking to contribute? Look for the Good First Issue label.

πŸ› Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

πŸ’‘ Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a πŸ‘. This helps maintainers prioritize what to work on.

See Feature Requests

❓ Questions

For questions related to using the library, please visit a support community instead of filing an issue on GitHub.

LICENSE

MIT

react-testing-library's People

Contributors

alejandronanez avatar alexkrolick avatar antsmartian avatar duncanleung avatar gnapse avatar gpx avatar jpavon avatar lgandecki avatar m0nica avatar maddijoyce avatar markpollmann avatar marktinsley avatar mbaranovski avatar michielnuyts avatar miklet avatar natenorberg avatar nickmccurdy avatar nickymeuleman avatar ovidiuch avatar pbomb avatar petermakowski avatar rbrtsmith avatar rkotze avatar roystons avatar silvenon avatar thchia avatar theneva avatar thymikee avatar timbonicus avatar vctormb avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.