-
-
Notifications
You must be signed in to change notification settings - Fork 26.9k
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
Document testing #538
Document testing #538
Changes from all commits
d3a0aeb
728046e
976a754
a036aa7
b995398
ad0e062
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ You can find the most recent version of this guide [here](https://github.com/fac | |
- [Folder Structure](#folder-structure) | ||
- [Available Scripts](#available-scripts) | ||
- [npm start](#npm-start) | ||
- [npm test](#npm-test) | ||
- [npm run build](#npm-run-build) | ||
- [npm run eject](#npm-run-eject) | ||
- [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) | ||
|
@@ -24,6 +25,17 @@ You can find the most recent version of this guide [here](https://github.com/fac | |
- [Integrating with a Node Backend](#integrating-with-a-node-backend) | ||
- [Proxying API Requests in Development](#proxying-api-requests-in-development) | ||
- [Adding `<meta>` Tags](#adding-meta-tags) | ||
- [Running Tests](#running-tests) | ||
- [Filename Conventions](#filename-conventions) | ||
- [Command Line Interface](#command-line-interface) | ||
- [Version Control Integration](#version-control-integration) | ||
- [Writing Tests](#writing-tests) | ||
- [Testing Components](#testing-components) | ||
- [Using Third Party Assertion Libraries](#using-third-party-assertion-libraries) | ||
- [Coverage Reporting](#coverage-reporting) | ||
- [Continuous Integration](#continuous-integration) | ||
- [Disabling jsdom](#disabling-jsdom) | ||
- [Experimental Snapshot Testing](#experimental-snapshot-testing) | ||
- [Deployment](#deployment) | ||
- [Now](#now) | ||
- [Heroku](#heroku) | ||
|
@@ -97,6 +109,11 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. | |
The page will reload if you make edits.<br> | ||
You will also see any lint errors in the console. | ||
|
||
### `npm test` | ||
|
||
Launches the test runner in the interactive watch mode. | ||
See the section about [running tests](#running-tests) for more information. | ||
|
||
### `npm run build` | ||
|
||
Builds the app for production to the `build` folder.<br> | ||
|
@@ -425,15 +442,15 @@ render() { | |
The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this | ||
value, we need to have it defined in the environment: | ||
|
||
### Windows (cmd.exe) | ||
#### Windows (cmd.exe) | ||
|
||
```cmd | ||
set REACT_APP_SECRET_CODE=abcdef&&npm start | ||
``` | ||
|
||
(Note: the lack of whitespace is intentional.) | ||
|
||
### Linux, OS X (Bash) | ||
#### Linux, OS X (Bash) | ||
|
||
```bash | ||
REACT_APP_SECRET_CODE=abcdef npm start | ||
|
@@ -522,6 +539,201 @@ Then, on the server, regardless of the backend you use, you can read `index.html | |
|
||
If you use a Node server, you can even share the route matching logic between the client and the server. However duplicating it also works fine in simple cases. | ||
|
||
## Running Tests | ||
|
||
>Note: this feature is available with `react-scripts@0.3.0` and higher. | ||
>[Read the migration guide to learn how to enable it in older projects!](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#migrating-from-023-to-030) | ||
|
||
Create React App uses [Jest](https://facebook.github.io/jest/) as its test runner. To prepare for this integration, we did a [major revamp](https://facebook.github.io/jest/blog/2016/09/01/jest-15.html) of Jest so if you heard bad things about it, give it another try. | ||
|
||
Jest is a Node-based runner. This means that the tests always run in a Node environment and not in a real browser. This lets us enable fast iteration speed and prevent flakiness. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we add "If you'd like to run real end-to-end tests, try selenium" or something? We should totally add a photo of the testing pyramid. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 as a follow up but I already spent a ton of time on this so will ship as is for now |
||
|
||
While Jest provides browser globals such as `window` thanks to [jsdom](https://github.com/tmpvar/jsdom), they are only approximations of the real browser behavior. Jest is intended to be used for unit tests of your logic and your components rather than the DOM quirks. | ||
|
||
We recommend that you use a separate tool for browser end-to-end tests if you need them. They are beyond the scope of Create React App. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, there it is. |
||
|
||
### Filename Conventions | ||
|
||
Jest will look for test files with any of the following popular naming conventions: | ||
|
||
* Files with `.js` suffix in `__tests__` folders. | ||
* Files with `.test.js` suffix. | ||
* Files with `.spec.js` suffix. | ||
|
||
The `.test.js` / `.spec.js` files (or the `__tests__` folders) can be located at any depth under the `src` top level folder. | ||
|
||
We recommend to put the test files (or `__tests__` folders) next to the code they are testing so that relative imports appear shorter. For example, if `App.test.js` and `App.js` are in the same folder, the test just needs to `import App from './App'` instead of a long relative path. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's also good to colocate tests with the modules they test so that they are easy to locate in larger projects. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
### Command Line Interface | ||
|
||
When you run `npm test`, Jest will launch in the watch mode. Every time you save a file, it will re-run the tests, just like `npm start` recompiles the code. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would recommend against watch mode being the default. Many CI systems will run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh dang. I just saw the note about how jest is smart and wont watch on CI... That's a super feature! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's something we do in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty amazing what you can do when you just forward npm scripts onto your own binary :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dan: wanna send a PR for that? I'm happy to ship that as a default. As we expand interactive watch, there are probably few reasons to not use it as a default. |
||
|
||
The watcher includes an interactive command-line interface with the ability to run all tests, or focus on a search pattern. It is designed this way so that you could keep it open and enjoy fast re-runs. You can learn the commands from the “Watch Usage” note that the watcher prints after every run: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "you can keep it open"? |
||
|
||
![Jest watch mode](http://facebook.github.io/jest/img/blog/15-watch.gif) | ||
|
||
### Version Control Integration | ||
|
||
By default, when you run `npm test`, Jest will only run the tests related to files changed since last commit. This is an optimization designed to make your tests runs fast regardless of how many tests you have. However it assumes that you don’t often commit the code that doesn’t pass the tests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
Jest will always explicitly mention that it only ran tests related to the files changed since the last commit. You can also press `a` in the watch mode to force Jest to run all tests. | ||
|
||
Jest will always run all tests on a [continuous integration](#continuous-integration) server or if the project is not inside a Git or Mercurial repository. | ||
|
||
### Writing Tests | ||
|
||
To create tests, add `it()` blocks with the name of the test and its code. You may optionally wrap them in `describe()` blocks for logical grouping but this is neither required nor recommended. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also provide a global |
||
|
||
Jest provides a built-in `expect()` global function for making assertions. A basic test could look like this: | ||
|
||
```js | ||
import sum from './sum'; | ||
|
||
it('sums numbers', () => { | ||
expect(sum(1, 2)).toEqual(3); | ||
expect(sum(2, 2)).toEqual(4); | ||
}); | ||
``` | ||
|
||
All `expect()` matchers supported by Jest are [extensively documented here](http://facebook.github.io/jest/docs/api.html#expect-value). | ||
You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](http://facebook.github.io/jest/docs/api.html#tobecalled) to create “spies” or mock functions. | ||
|
||
### Testing Components | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this also highlight that people shouldn't waste time writing tests for components while they don't even know what the component is gonna be like? I don't want to be too prescriptive but component testing and especially snapshot testing is a way for me to lock in behavior that I carefully designed previously. This way I get maximum time and focus building the thing I want and minimal time creating a test (just render + toMatchSnapshot or whatever). This is totally the opposite for store/reducer tests and I think it is worth mentioning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let’s do all of this as follow up, I really want to ship now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw I’d be grateful if you could just do a pass over all of this in one PR and change anything you like |
||
|
||
There is a broad spectrum of component testing techniques. They range from a “smoke test” verifying that a component renders without throwing, to shallow rendering and testing some of the output, to full rendering and testing component lifecycle and state changes. | ||
|
||
Different projects choose different testing tradeoffs based on how often components change, and how much logic they contain. If you haven’t decided on a testing strategy yet, we recommend that you start with creating simple smoke tests for your components: | ||
|
||
```js | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import App from './App'; | ||
|
||
it('renders without crashing', () => { | ||
const div = document.createElement('div'); | ||
ReactDOM.render(<App />, div); | ||
}); | ||
``` | ||
|
||
This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.js`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "lot of value" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PR? :-) |
||
|
||
When you encounter bugs caused by changing components, you will gain a deeper insight into which parts of them are worth testing in your application. This might be a good time to introduce more specific tests asserting specific expected output or behavior. | ||
|
||
If you’d like to test components in isolation from the child components they render, we recommend using [`shallow()` rendering API](http://airbnb.io/enzyme/docs/api/shallow.html) from [Enzyme](http://airbnb.io/enzyme/). You can write a smoke test with it too: | ||
|
||
```sh | ||
npm install --save-dev enzyme react-addons-test-utils | ||
``` | ||
|
||
```js | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import App from './App'; | ||
|
||
it('renders without crashing', () => { | ||
shallow(<App />); | ||
}); | ||
``` | ||
|
||
Unlike the previous smoke test using `ReactDOM.render()`, this test only renders `<App>` and doesn’t go deeper. For example, even if `<App>` itself renders a `<Button>` that throws, this test will pass. Shallow rendering is great for isolated unit tests, but you may still want to create some full rendering tests to ensure the components integrate correctly. Enzyme supports [full rendering with `mount()`](http://airbnb.io/enzyme/docs/api/mount.html), and you can also use it for testing state changes and component lifecyle. | ||
|
||
You can read the [Enzyme documentation](http://airbnb.io/enzyme/) for more testing techniques. Enzyme documentation uses Chai and Sinon for assertions but you don’t have to use them because Jest provides built-in `expect()` and `jest.fn()` for spies. | ||
|
||
Here is an example from Enzyme documentation that asserts specific output, rewritten to use Jest matchers: | ||
|
||
```js | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import App from './App'; | ||
|
||
it('renders welcome message', () => { | ||
const wrapper = shallow(<App />); | ||
const welcome = <h2>Welcome to React</h2>; | ||
// expect(wrapper.contains(welcome)).to.equal(true); | ||
expect(wrapper.contains(welcome)).toEqual(true); | ||
}); | ||
``` | ||
|
||
All Jest matchers are [extensively documented here](http://facebook.github.io/jest/docs/api.html#expect-value). | ||
Nevertheless you can use a third-party assertion library like Chai if you want to, as described below. | ||
|
||
### Using Third Party Assertion Libraries | ||
|
||
We recommend that you use `expect()` for assertions and `jest.fn()` for spies. If you are having issues with them please [file those against Jest](https://github.com/facebook/jest/issues/new), and we’ll fix them. We intend to keep making them better for React, supporting, for example, [pretty-printing React elements as JSX](https://github.com/facebook/jest/pull/1566). | ||
|
||
However, if you are used to other libraries, such as [Chai](http://chaijs.com/) and [Sinon](http://sinonjs.org/), or if you have existing code using them that you’d like to port over, you can import them normally like this: | ||
|
||
```js | ||
import sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
``` | ||
|
||
and then use them in your tests like you normally do. | ||
|
||
### Coverage Reporting | ||
|
||
Jest has an integrated coverage reporter that works well with ES6 and requires no configuration. | ||
Run `npm test -- --coverage` (note extra `--` in the middle) to include a coverage report like this: | ||
|
||
![coverage report](http://i.imgur.com/5bFhnTS.png) | ||
|
||
Note that tests run much slower with coverage so it is recommended to run it separately from your normal workflow. | ||
|
||
### Continuous Integration | ||
|
||
By default `npm test` runs the watcher with interactive CLI. However, you can force it to run tests once and finish the process by setting an environment variable called `CI`. Popular CI servers already set it by default but you can do this yourself too: | ||
|
||
#### Windows (cmd.exe) | ||
|
||
```cmd | ||
set CI=true&&npm test | ||
``` | ||
|
||
(Note: the lack of whitespace is intentional.) | ||
|
||
#### Linux, OS X (Bash) | ||
|
||
```bash | ||
CI=true npm test | ||
``` | ||
|
||
This way Jest will run tests once instead of launching the watcher. | ||
|
||
If you find yourself doing this often in development, please [file an issue](https://github.com/facebookincubator/create-react-app/issues/new) to tell us about your use case because we want to make watcher the best experience and are open to changing how it works to accommodate more workflows. | ||
|
||
### Disabling jsdom | ||
|
||
By default, the `package.json` of the generated project looks like this: | ||
|
||
```js | ||
// ... | ||
"scripts": { | ||
// ... | ||
"test": "react-scripts test --env=jsdom" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's interesting! I haven't followed CRA too closely, but this appears to be the first instance of anything that resembles config. Is this the decided upon method of config? Is this something to be concerned about? I realize that this is a pretty useful optimization. Just wanted to call it out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’s mostly there to stick out so people are incentivized to figure out ways to test their stuff without jsdom. 😉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future, as mentioned, we’ll be recommending snapshot testing, and when it becomes the default test we ship, we’ll remove |
||
} | ||
``` | ||
|
||
If you know that none of your tests depend on [jsdom](https://github.com/tmpvar/jsdom), you can safely remove `--env=jsdom`, and your tests will run faster. | ||
To help you make up your mind, here is a list of APIs that **need jsdom**: | ||
|
||
* Any browser globals like `window` and `document` | ||
* [`ReactDOM.render()`](https://facebook.github.io/react/docs/top-level-api.html#reactdom.render) | ||
* [`TestUtils.renderIntoDocument()`](https://facebook.github.io/react/docs/test-utils.html#renderintodocument) ([a shortcut](https://github.com/facebook/react/blob/34761cf9a252964abfaab6faf74d473ad95d1f21/src/test/ReactTestUtils.js#L83-L91) for the above) | ||
* [`mount()`](http://airbnb.io/enzyme/docs/api/mount.html) in [Enzyme](http://airbnb.io/enzyme/index.html) | ||
|
||
In contrast, **jsdom is not needed** for the following APIs: | ||
|
||
* [`TestUtils.createRenderer()`](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) (shallow rendering) | ||
* [`shallow()`](http://airbnb.io/enzyme/docs/api/shallow.html) in [Enzyme](http://airbnb.io/enzyme/index.html) | ||
|
||
Finally, jsdom is also not needed for [snapshot testing](http://facebook.github.io/jest/blog/2016/07/27/jest-14.html). Longer term, this is the direction we are interested in exploring, but snapshot testing is [not fully baked yet](https://github.com/facebookincubator/create-react-app/issues/372) so we don’t officially encourage its usage yet. | ||
|
||
### Experimental Snapshot Testing | ||
|
||
Snapshot testing is a new feature of Jest that automatically generates text snapshots of your components and saves them on the disk so if the UI output changes, you get notified without manually writing any assertions on the component output. | ||
|
||
This feature is experimental and still [has major usage issues](https://github.com/facebookincubator/create-react-app/issues/372) so we only encourage you to use it if you like experimental technology. We intend to gradually improve it over time and eventually offer it as the default solution for testing React components, but this will take time. [Read more about snapshot testing.](http://facebook.github.io/jest/blog/2016/07/27/jest-14.html) | ||
|
||
## Deployment | ||
|
||
By default, Create React App produces a build assuming your app is hosted at the server root. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too. "We did a major revamp to improve new user experience"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we not? “Jest is bad” is well and alive, no need to look further than the recent issue. I do think we need to address this concern in text. I don’t think it makes us look bad, just honest about past problems and our willingness to solve them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense. Can we maybe say "The only resemblance of Jest with the test framework from a year ago is its name?" That sounds more uplifting. I just wanna avoid giving people a bad impression if they haven't even heard about Jest before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s discuss in a follow up PR if you’d like to make a pass over this. But as you know, it’s better to under-promise and over-deliver. I would give it a few months and then revisit when the perception is turned around thanks to word of mouth.