Skip to content

Testing Docs #2165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions content/community/tools-testing.md

This file was deleted.

105 changes: 105 additions & 0 deletions content/docs/act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
id: act
title: act
permalink: docs/act.html
---

Tests typically resemble the [arrange / act / assert design pattern](http://wiki.c2.com/?ArrangeActAssert). When writing UI tests, tasks like rendering, user events, or data fetching can be considered as 'units' of interaction with a user interface. `act()` provides an abstraction to grouping these actions, and making sure the rendering surface reflects a state where all these actions have been 'committed' (ie, the DOM has been updated). For example:

```jsx
import { render } from "react-dom";
import { act } from "react-dom/test-utils";
import App from "./app";

it("should render content", () => {
const container = document.createElement("div");
act(() => {
render(<App />, container);
});
// ... make assertions about the UI
});
```

### Guarantees {#guarantees}

`act()` provides some guarantees when it's run.

- It batches updates triggered inside its scope, and applies them all in one go.
- It flushes any queued effects and updates.
- When nested, the work is flushed only on exiting the outermost `act()`.
- The asynchronous version of `act()` also flushes any resolved promises.

These guarantees ensure that tests can assert on a 'stable' state of the UI without missing any updates.

### Warnings {#warnings}

When running tests with jest, React triggers warnings for test code that should be wrapped with `act()`.

- Code that triggers a Hook-based state update.
- In strict mode, code that triggers an Effect Hook.

If you are seeing these warnings, it's possible that your test will is making assertions on an unreliable state of the UI, before it's had a chance to stabilise. Wrapping the test code that triggers these conditions with `act()` will avoid this problem and silence the warnings.

### Asynchronous Testing {#asynchronous-testing}

When working with asynchronous code, an async version of `act()` can be used to flush resolved promises and write assertions on asynchronous updates.

```jsx
import {act} from 'react-dom/test-utils'
import App from './app'
it('flushes microtasks', () => {
await act(async () => {
render(<App/>, document.createElement('div'))
}) // guarantees that all resolved promises will be flushed after this call
})
```

### Nested `act()`s {#nested-acts}

When `act()` calls are nested, React will flush queued work on exiting the outermost `act()` call. This lets you compose sequences of UI changes, guaranteeing that they'll still be batched together as one. For example, consider a helper function that dispatches clicks on elements.

```jsx
function click(element) {
act(() => {
element.dispatchEvent(
new MouseEvent("click", { bubbles: true })
);
});
}
```

You could use this in tests:

```jsx
// ...
click(element);
// then assert on the side effects of the click
// ...
```

However, if we need to batch multiple clicks as one 'unit', we can wrap them with another `act()` call.

```jsx
act(() => {
click(element);
click(element);
click(element);
}); // guarantees that updates will be batched and run as one unit
```

### Multiple renderers {#multiple-renderers}

In rare cases, you may be running a test on a component that uses multiple renderers. For example, you may be running snapshot tests on a component with `react-test-renderer`, that uses `ReactDOM.render` inside a child component to render some content. In this scenario, you can wrap updates with `act()`s corresponding to their renderers.

```jsx
import { act as domAct } from "react-dom/test-utils";
import { act as testAct, create } from "react-test-renderer";
// ...
let root;
domAct(() => {
testAct(() => {
root = create(<App />);
});
});
expect(root).toMatchSnapshot();
```
2 changes: 2 additions & 0 deletions content/docs/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
title: Static Type Checking
- id: strict-mode
title: Strict Mode
- id: testing
title: Testing
- id: typechecking-with-proptypes
title: Typechecking With PropTypes
- id: uncontrolled-components
Expand Down
61 changes: 61 additions & 0 deletions content/docs/testing-environments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
id: testing-environments
title: Testing environments
permalink: docs/testing-environments.html
---

<!-- This document is intended for folks who are comfortable with Javascript, and have probably written tests with it. It acts as a reference for the differences in testing environments for React components, and how those differences affect the tests that they write. This document also assumes a slant towards web-based react-dom components, but has notes for other renderers. -->

Testing environments can affect the way you write and run tests for React Components.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds a bit like a vague truism. "Can affect", so..?

Maybe could be a bit more concrete about what people will get out of this document.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the next line cover that?


This document goes through the major factors that can affect your environment, the implications of those decisions, and recommendations for some scenarios.

### Test frameworks {#test-frameworks}

Test frameworks like [Jest](https://jestjs.io/), [mocha](https://mochajs.org/), [ava](https://github.com/avajs/ava) let you write test suites as regular Javascript, and run them as part of your development process. Additionally, test suites are run as part of workflows like builds, CI/CD, etc.

- Jest is widely compatible with React projects, supporting features like mocked [modules](#mocking-modules) and [timers](#mocking-timers), [jsdom](#mocking-a-rendering-surface) support, etc.
- Libraries like `mocha` work well in real browser environments (when combined with a browser test runner like [Karma](https://karma-runner.github.io)), and could help for tests that explicitly need it.
- End-to-End tests are used for testing longer flows across multiple pages, and require a [different setup](#end-to-end-tests-aka-e2e-tests)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a sentence that suggests how much to test UI components, and when we recommend to use unit / e2e testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really recommend 'unit' testing in the traditional sense anyway. On L29 I mention how most UI tests are done with a jest/jsdom setup.

### Mocking a rendering surface {#mocking-a-rendering-surface}

Tests could run in an environment without access to a 'real' rendering surface. (eg: React Native or ReactDOM based components could be tested on Node.js) Depending on the type of test, you could simulate a browser-like DOM with something like [jsdom](https://github.com/jsdom/jsdom), or use [`react-test-renderer`](https://reactjs.org/docs/test-renderer.html) to render your components.

- [`jsdom`](https://github.com/jsdom/jsdom) is a lightweight browser implementation that's _mostly_ spec compliant, but runs inside Node.js. It behaves like a regular browser would, but doesn't have features like [layout and navigation](https://github.com/jsdom/jsdom#unimplemented-parts-of-the-web-platform). This is still useful for most web-based component tests, since it runs quicker than having to start up a browser for each test. It also runs in the same process as your tests, so you can write code to examine and assert on the rendered DOM.

- Just like in a real browser, `jsdom` lets us model user interactions; tests can dispatch 'real' events on DOM nodes, and then observe and assert on the side effects of these actions on a rendered surface.[<sup>(example)</sup>](/docs/testing-recipes.html#events)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sup tags are pretty hard to read in the rendered version. They feel very noisy. Can we restructure this so we won't need them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I dislike them too. Will rewrite.


- A large portion of UI tests can be written with the above setup: using Jest as a test runner, rendered to `jsdom`, with user interactions specified as sequences of browser events, powered by [`act()`](/docs/act.html)[<sup>(examples)</sup>](/docs/testing-recipes.html). For example, a lot of React's own tests are written with this combination.

- If you're writing a library that tests mostly browser-specific behaviour, and requires 'native' browser behaviour like layout, you could use a framework like [mocha](https://mochajs.org/) in combination with [Karma](https://karma-runner.github.io) to run your tests in a browser.[<sup>(example)</sup>](packages/mocha/README.md)

- In an environment where you _can't_ simulate a DOM (eg: testing React Native components on Node.js), you could use [event simulation helpers](https://reactjs.org/docs/test-utils.html#simulate) to simulate interactions with elements. Alternatively, you could use the `fireEvent` helper from [`@testing-library/react-native`](https://testing-library.com/docs/native-testing-library).

- Frameworks like [Cypress](https://www.cypress.io/), [puppeteer](https://github.com/GoogleChrome/puppeteer) and [webdriver](https://www.seleniumhq.org/projects/webdriver/) are useful for running [end-to-end tests](#end-to-end-tests-aka-e2e-tests) (puppeteer and webdriver both run in a separate process from the actual code being tested).

### Mocking functions {#mocking-functions}

When writing tests, we'd like to mock out the parts of our code that don't have equivalents inside our testing environment (eg: checking `navigator.onLine` status inside Node.js). Tests could also spy on some functions, and observe how other parts of the test interact with them. It is then useful to be able to selectively mock these functions with test-friendly versions.

Particularly so for mocking data fetching infrastructure; consuming 'fake' data for tests wihtout fetching from 'real' (possibly flaky) api endpoints[<sup>(example)</sup>](/docs/testing-recipes.html#data-fetching) make tests predictable. Libraries like [Jest](https://jestjs.io/) and [sinon](https://sinonjs.org/), among others, support mocked functions. It's harder to do so for end-to-end tests.

### Mocking modules {#mocking-modules}

Some components have dependencies for modules that may not work well in test environments, or aren't essential to our tests. It can be useful to selectively mock these modules out with suitable replacements.[<sup>(example)</sup>](/docs/testing-recipes.html#mocking-modules).

- On Node.js, runners like Jest supports mocking modules. You can also use libraries like [`mock-require`](https://www.npmjs.com/package/mock-require).[<sup>(example)</sup>](packages/mock-require/README.md)

- Testing environments without a module mocking mechanism (like browsers) might have to manually setup their builds to alias modules, possibly globally for all their tests. [<sup>(example)</sup>](packages/aliased-mocha-browser) Notably, [end-to-end tests](#end-to-end-tests-aka-e2e-tests) might have a harder time setting up module mocks.

### Mocking timers {#mocking-timers}

Components might be using time based functions `setTimeout`, `setInterval`, `Date.now`, etc. In testing environments, it's advantageous to mock these functions out with replacements that can be manually advanced and resolved. This is great for making sure your tests run fast! Tests that are dependent on timers resolve in order, but quicker.[<sup>(example)</sup>](/docs/testing-recipes.html#timers) Most frameworks, including [Jest](https://jestjs.io/docs/en/timer-mocks), [sinon](https://sinonjs.org/releases/v7.3.2/fake-timers/) and [lolex](https://github.com/sinonjs/lolex) let you mock timers in your tests.

- A pattern that's harder without mocked timers, is waiting for all 'live' timers to resolve, and then for the UI to render. A solution is waiting for actual elements to appear/change on the rendering surface[<sup>(example)</sup>](/docs/testing-recipes.html#elements). Tests could also 'await' specific periods, but makes them subject to change when the component's behaviour changes[<sup>(example)</sup>](/docs/testing-recipes.html#real-timers).

- You may not want to mock timers for testing something that's dependent on the 'real' time passing. Examples include animation tests, or if you're interacting with a timing specific endpoint (like an api rate-limiter). Libraries with timer mocks let you enable and disable them on a per test/suite basis, so you can explicitly choose how these tests would run.

### End To End tests (aka 'e2e' tests) {#end-to-end-tests-aka-e2e-tests}

End To End tests are useful for testing longer workflows; tests that not only need a 'real' browser to render their components, but probably also fetch data from 'real' api endpoints, uses sessions and cookies in the browser, navigating between different links and asserting not just on the DOM state, but also backing data (like a database, to verify if updates happened as expected). In this scenario, you would use a framework like [Cypress](https://www.cypress.io/) or a library like [puppeteer](https://github.com/GoogleChrome/puppeteer) and move between multiple 'pages'/routes and assert on side effects not just in the browser, but maybe on the backend too.[<sup>(example)</sup>](packages/e2e/README.md)
Loading