Skip to content

Commit 869a24c

Browse files
committed
Add README and comments
1 parent aceec5f commit 869a24c

File tree

4 files changed

+79
-6
lines changed

4 files changed

+79
-6
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Redux-Form Test
2+
3+
This project shows how to do unit and integrations tests with Redux-Form.
4+
5+
## Basic premise
6+
7+
Ideally you should do both **unit tests** and **integration tests** with your form components.
8+
9+
First, make sure you understand the general idea of testing connected components from [the Redux "Writing Tests" doc](http://redux.js.org/docs/recipes/WritingTests.html). Search for the section called **Connected Components**.
10+
11+
So, you should have a "dumb" (aka presentational) React component that is separate from any connection to Redux and Redux-Form. It takes props. That's it. That's your **unit tests**.
12+
13+
You should also have a container React component that connects the presentational component to Redux and Redux-Form. To test this, your test becomes an **integration test** because you're hooking up your presentational component to redux's store. You're integrating them.
14+
15+
To make this separation clear, I named the presentational component `ContactFormComponent`, and the container `ContactFormContainer`, in 2 different files, although you could also set it up so they are in one file.
16+
17+
## What should I look at?
18+
19+
This project is very simple. Take a look at the `tests` directory and follow the code into the `app` directory. I recommend you look at the `tests/unit` directory first, and then `tests/integration`, because the former is simpler and is a basis for the latter.
20+
21+
The test files are commented with specific pointers.
22+
23+
## How to run the tests
24+
25+
```
26+
$ npm install
27+
$ npm run test
28+
```
29+
30+
## How to run the site
31+
32+
There is no site! These are just tests! The "contact form" component is trivially simple: It has a first name label, an input for the first name, and a submit button.
33+
34+
## Technologies
35+
36+
These tests are written to run with the mocha test framework. I use the chai assertion library to make it more readable. (Additionally, chai-enzyme and sinon-chai are also used.) But what I'm trying to show off is testing Redux-Form, so of course you could use a different test framework.
37+
38+
Similarly, I'm using [Enzyme](http://airbnb.io/enzyme/), which is far better than the basic [Facebook React Test Utils](https://facebook.github.io/react/docs/test-utils.html). Enzyme is more convenient and intuitive to write, easier to read, and more powerful with great debug helpers. I'm using sinon as a spy library, which is how we know if the functions we pass into our components get called properly. You could exchange sinon for another spy library.
39+
40+
## License
41+
42+
MIT

app/index.js

Whitespace-only changes.

tests/integration/index.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
import ContactFormContainer from '../../app/ContactFormContainer'
2+
import React from 'react'
3+
4+
// See README for discussion of chai, enzyme, and sinon
25
import chai, { expect } from 'chai'
36
import sinonChai from 'sinon-chai'
47
import { mount } from 'enzyme'
58
import chaiEnzyme from 'chai-enzyme'
9+
import sinon from 'sinon'
10+
11+
chai.use(sinonChai)
12+
chai.use(chaiEnzyme())
13+
14+
// In this file we're doing an integration test. Thus we need to hook up our
15+
// form component to Redux and Redux-Form. To do that, we need to create the
16+
// simplest redux store possible that will work with Redux-Form.
617
import { reducer as formReducer } from 'redux-form'
718
import { createStore, combineReducers } from 'redux'
8-
import sinon from 'sinon'
9-
import React from 'react'
19+
20+
// To test the entire component, we're going to use Enzyme's `mount` method,
21+
// which is the opposite of shallow rendering. To use `mount`, we need to have
22+
// a DOM, so we use jsdom. You can alternatively run these tests in a browser
23+
// to get a DOM, but that's more complicated to set up and usually slower.
1024
import jsdom from 'jsdom'
1125
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>')
1226
global.window = document.defaultView
1327

14-
chai.use(sinonChai)
15-
chai.use(chaiEnzyme())
16-
1728
describe("ContactFormContainer", () => {
1829
let store = null
1930
let onSave = null
@@ -30,6 +41,10 @@ describe("ContactFormContainer", () => {
3041
})
3142
it("shows help text when first name is set to blank", () => {
3243
const input = subject.find('input').first()
44+
// Our form component only shows error messages (help text) if the
45+
// field has been touched. To mimic touching the field, we simulate a
46+
// blur event, which means the input's onBlur method will run, which
47+
// will call the onBlur method supplied by Redux-Form.
3348
input.simulate('blur')
3449
const firstNameHelpBlock = subject.find('.help-block')
3550
expect(firstNameHelpBlock).to.exist
@@ -39,6 +54,9 @@ describe("ContactFormContainer", () => {
3954
it("calls onSave", () => {
4055
const form = subject.find('form')
4156
const input = subject.find('input').first()
57+
// Our form, when connected to Redux-Form, won't submit unless it's
58+
// valid. Thus, we type a first name here to make the form's inputs,
59+
// and thus the form, valid.
4260
input.simulate('change', { target: { value: 'Joe' } })
4361
form.simulate('submit')
4462
expect(onSave).to.have.been.called

tests/unit/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import ContactFormComponent from '../../app/ContactFormComponent'
2+
import React from 'react'
3+
4+
// See README for discussion of chai, enzyme, and sinon
25
import chai, { expect } from 'chai'
36
import sinonChai from 'sinon-chai'
47
import { shallow } from 'enzyme'
58
import chaiEnzyme from 'chai-enzyme'
69
import sinon from 'sinon'
7-
import React from 'react'
810

911
chai.use(sinonChai)
1012
chai.use(chaiEnzyme())
1113

14+
// In this file we're doing unit testing of our component, which means it
15+
// really has nothing to do with Redux-Form at this point. We can pass in our
16+
// own props (e.g. `submitting`) and make sure our form renders as we expect.
17+
1218
describe("ContactFormComponent", () => {
1319
let onSave = null
1420
let subject = null
@@ -21,6 +27,10 @@ describe("ContactFormComponent", () => {
2127
const props = {
2228
onSave,
2329
submitting: submitting,
30+
// The real redux form has many properties for each field,
31+
// including onChange and onBlur handlers. We only need to provide
32+
// the ones we care about, which for these tests are basically
33+
// none.
2434
fields: {
2535
firstName: {
2636
value: ''
@@ -32,6 +42,7 @@ describe("ContactFormComponent", () => {
3242
return shallow(<ContactFormComponent {...props}/>)
3343
}
3444

45+
// Here we show we can test asychronous actions triggered by our form.
3546
it("calls resetForm after onSave", (done) => {
3647
subject = buildSubject()
3748
subject.find('form').simulate('submit')
@@ -42,6 +53,8 @@ describe("ContactFormComponent", () => {
4253
})
4354
})
4455

56+
// This is a very simle test, making sure that if we pass in a certain
57+
// prop value, our form renders appropriately.
4558
context("when submitting", () => {
4659
it("shows a spinner while submitting", () => {
4760
submitting = true

0 commit comments

Comments
 (0)