new)
git clone https://github.com/QuentinRoy/jest-tutorial.git
cd jest-tutorial
npm install
npm testnpm startlet nextTodoId = 0
export const addTodo = (text) => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const toggleTodo = (id) => ({
type: 'TOGGLE_TODO',
id
})import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})
describe defines a test suite
import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})it defines a test
import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})expect creates an expectation
of its argument
import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})Expectations are chained with matchers
import * as actions from './index'
describe('todo actions', () => {
it('addTodo should create ADD_TODO action', () => {
expect(actions.addTodo('Use Redux')).toEqual({
type: 'ADD_TODO',
id: 0,
text: 'Use Redux'
})
})
it('toggleTodo should create TOGGLE_TODO action', () => {
expect(actions.toggleTodo(1)).toEqual({
type: 'TOGGLE_TODO',
id: 1
})
})
})toBe()toEqual()toContain()toBeLessThan()toThrowError()const todos = (state = [], action) => {
switch (action.type) {
default:
return state
}
}
export default todosimport todos from './index'
describe('todos reducer', () => {
it('ignores unknown actions', () => {
const unknownAction = { type: undefined }
const initialState = []
expect(todos(initialState, unknownAction)).toBe(initialState)
})
})todos reducer should handle 'ADD_TODO' actions:{id, text, completed} object to the statecompleted should be false by defaultimport todos from './index'
describe('todos reducer', () => {
[...] it('creates a new todo on ADD_TODO actions', () => {
const action = { type: 'ADD_TODO', id: 42, text: 'whatever' }
const initialState = [
{ id: 1, text: 'foo', completed: true }
]
expect(todos(initialState, action)).toEqual([
{ id: 1, text: 'foo', completed: true },
{ id: 42, text: 'whatever', completed: false }
])
})
})git checkout add-todoconst todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
default:
return state
}
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
]
default:
return state
}
}
export default todostodos reducer should handle 'TOGGLE_TODO' actions:idcompleted attributeimport todos from './index'
describe('todos reducer', () => {[...] it('toggle the right todo on TOGGLE_TODO actions', () => {
const action = { type: 'TOGGLE_TODO', id: 2 }
const initialState = [
{ id: 1, text: 'foo', completed: false },
{ id: 2, text: 'bar', completed: false }
]
expect(todos(initialState, action)).toEqual([
{ id: 1, text: 'foo', completed: false },
{ id: 2, text: 'bar', completed: true }
])
})
})git checkout add-todo

Find the error
const todo = (state, action) => {
switch (action.type) {[...] case 'TOGGLE_TODO':
if (state.id === action.id) {
return state
}
return {
...state,
completed: !state.completed
}
default:
return state
}
}[...]describe('<MyComponent />', () => {
it('renders three .foo', () => {
// Create the component.
var component = React.addons.TestUtil.renderIntoDocument(MyComponent)
// Fetch its DOM node.
var componentElement = ReactDOM.findDOMNode(component)
// Check the .foo.
expect(componentElement.querySelectorAll('.foo').length).toBe(3)
})
})describe('<MyComponent />', () => {
it('renders three .foo', () => {
// Create the component.
var component = React.addons.TestUtil.renderIntoDocument(MyComponent)
// Fetch its DOM node.
var componentElement = ReactDOM.findDOMNode(component)
// Check the .foo.
expect(componentElement.querySelectorAll('.foo').length).toBe(3)
})
it('renders an .icon-star', () => {
var component = React.addons.TestUtil.renderIntoDocument(MyComponent)
var componentElement = ReactDOM.findDOMNode(component)
expect(componentElement.querySelectorAll('.icon-star').length).toBe(1)
})
it('renders this', () => { /* ... */ });
it('and that', () => { /* ... */ });
it('and a chicken', () => { /* ... */ });
})describe('<MyComponent />', () => {
it('renders correctly', () => {
const tree = renderer.create(
<Component />
).toJSON()
expect(tree).toMatchSnapshot()
})
})import React from 'react'
import renderer from 'react-test-renderer'
import Todo from './Todo'
describe('<Todo />', () => {
it('renders correctly when completed', () => {
const tree = renderer.create(
<Todo
completed
text="bar"
onClick={()=>{}}
/>
).toJSON()
expect(tree).toMatchSnapshot()
})
it('renders correctly when not completed', () => {
// ...
})
})import React from 'react'
import renderer from 'react-test-renderer'
import Todo from './Todo'
describe('<Todo />', () => {
it('renders correctly when completed', () => {
const tree = renderer.create(
<Todo
completed
text="bar"
onClick={()=>{}}
/>
).toJSON()
expect(tree).toMatchSnapshot()
})
it('renders correctly when not completed', () => {
// Try it!
})
})import React from 'react'
import renderer from 'react-test-renderer'
import { shallow } from 'enzyme'
import Todo from './Todo'
describe('<Todo />', () => {[...] it('calls onClick when clicked', () => {
const wrapper = shallow(
<Todo
completed
text="foo"
onClick={()=>{}}
/>
).simulate('click', 'click arguments')
// Now what?
})
})import React from 'react'
import renderer from 'react-test-renderer'
import { shallow } from 'enzyme'
import Todo from './Todo'
describe('<Todo />', () => {[...] it('calls onClick when clicked', () => {
// Create a mock function for the onClick handler.
const onClick = jest.fn()
const wrapper = shallow(
<Todo
completed
text="foo"
onClick={onClick}
/>
).simulate('click', 'click args')
// Check how the onClick mock has been called.
expect(onClick.mock.calls).toEqual([
['click args']
])
})<TodoList> is a containerconnectimport React from 'react'
import renderer from 'react-test-renderer'
import configureMockStore from 'redux-mock-store'
import TodoList from './TodoList'
const mockStore = configureMockStore()
describe('<TodoList />', () => {
it('renders correctly', () => {
// Define the initial state.
const state = [
{ id: 1, text: 'foo', completed: false },
{ id: 2, text: 'bar', completed: true }
]
// Create the store with this state.
const store = mockStore(state)
// Renders <TodoList> with this store.
const tree = renderer.create(
<TodoList
store={store}
/>
).toJSON()
expect(tree).toMatchSnapshot()
})
})import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import Todo from '../components/Todo'
import { toggleTodo } from '../actions'
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)[...]Is it properly isolated?
<TodoList> test depends on <Todo><Todo> breaks, it breaks<Todo> changes, it must change tooimport mocking!import React from 'react'
import renderer from 'react-test-renderer'
import TodoList from './TodoList'
import configureMockStore from 'redux-mock-store'
// Mock the Todo module and replace its export by a <Todo> tag.
jest.mock('../components/Todo', () => 'Todo')
// Also mock the toggleTodo action from the actions module.
jest.mock(
'../actions',
() => ({ toggleTodo: jest.fn() })
);
const mockStore = configureMockStore()
describe('<TodoList />', () => {[...]}) Test the behavior of <TodoList>
when a <Todo> is clicked