Things that you need to install:
- NPM / Yarn
- create-react-app
- Jest
- Enzyme
- Code Editor
- Coding of features and tests go hand in hand.
- Step :
- Write a unit test
- Run the test. See it fail
- Write the feature code to pass the test
- Refactor the code
- Repeat when you want to develop any feature
- It reducers errors and defects in the long run.
- It leads to higher quality code.
- A test runner made by Facebook
- Write test in the tests folder or with test.js
- Snapshot testing, coverage, and mocking
- A test utility library made by Airbnb
- Manipulate React components and DOM Behavior
- Jest + Enzyme = TDD with React
- First we need to create test for a app component
import React from 'react'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import toJson from 'enzyme-to-json'
import App from './App'
configure({adapter: new Adapter()})
const app = shallow(<App />)
it('renders correctly', () => {
expect(toJson(app)).toMatchSnapshot()
})
it('initialized the `state` with an empty list of gifts', () => {
expect(app.state().gifts).toEqual([])
})
it('adds a new gift to `state` when clicking the `add gift` button', () => {
app.find('.btn-add').simulate('click')
expect(app.state().gifts).toEqual([{ id: 1 }])
})
it('add a new gift to the rendered list when clicking the `add gift` button', () => {
app.find('.btn-add').simulate('click')
expect(app.find('.gift-list').children().length).toEqual(2)
})
- Second we want to create the app component so that the test will passed
import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
class App extends Component{
constructor() {
super()
this.state = { gifts: [] }
}
addGift = () => {
const { gifts } = this.state
const ids = this.state.gifts.map(gift => gift.id)
const max_id = ids.length > 0 ? Math.max(...ids) : 0
gifts.push({ id: max_id+1 })
this.setState({ gifts })
}
render() {
return(
<div>
<h2>Gift Giver</h2>
<div className="gift-list">
{
this.state.gifts.map(gift => {
return(
<div key={gift.id}></div>
)
})
}
</div>
<Button
className="btn-add"
onClick={this.addGift}
>
Add Gift
</Button>
</div>
)
}
}
export default App
- A variation of TDD that tests for user scenarios
- Given, When, Then...
- For Ex: Given notes, When deleting, Then remove a note
- BDD consists of scenarios/specifications
import React from 'react'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import toJson from 'enzyme-to-json'
import App from './App'
configure({adapter: new Adapter()})
describe('<App />', () => {
const app = shallow(<App />)
it('renders correctly', () => {
expect(toJson(app)).toMatchSnapshot()
})
it('initialized the `state` with an empty list of gifts', () => {
expect(app.state().gifts).toEqual([])
})
describe('when clicking the `add gift` button', () => {
beforeEach(() => {
app.find('.btn-add').simulate('click')
})
afterEach(() => {
app.setState({ gifts: [] })
})
it('adds a new gift to `state`', () => {
expect(app.state().gifts).toEqual([{ id: 1 }])
})
it('add a new gift to the rendered list', () => {
expect(app.find('.gift-list').children().length).toEqual(1)
})
})
})
With BDD, all the involved parties have a strong understanding of the project and they can all have a role in communication and actually have constructive discussions. BDD increases and improves collaboration. It enables everyone involved in the project to easily engage with the product development cycle. And by using plain language, all are able to write behavior scenarios.
note: By using a language understood by all, everyone gets a strong visibility into the project’s progression.
import React from 'react'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import toJson from 'enzyme-to-json'
import App from './App'
configure({adapter: new Adapter()})
describe('<App />', () => {
const app = shallow(<App />)
it('renders correctly', () => {
expect(toJson(app)).toMatchSnapshot()
})
it('initialized the `state` with an empty list of gifts', () => {
expect(app.state().gifts).toEqual([])
})
describe('when clicking the `add gift` button', () => {
const id = 1
beforeEach(() => {
app.find('.btn-add').simulate('click')
})
afterEach(() => {
app.setState({ gifts: [] })
})
it('adds a new gift to `state`', () => {
expect(app.state().gifts).toEqual([{ id }])
})
it('add a new gift to the rendered list', () => {
expect(app.find('.gift-list').children().length).toEqual(1)
})
it('creates a Gift component', () => {
expect(app.find('Gift').exists()).toBe(true)
})
})
describe('and the user wants to remove the added gift', () => {
const id = 1
beforeEach(() => {
app.instance().removeGift(id)
})
it('removes the gift from `state`', () => {
expect(app.state().gifts).toEqual([])
})
})
})
import React from 'react'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import toJson from 'enzyme-to-json'
import Gift from './Gift'
configure({adapter: new Adapter()})
describe('Gift', () => {
const mockRemove = jest.fn()
const id = 1
const props = { gift: { id }, removeGift: mockRemove }
const gift = shallow(<Gift {...props} />)
it('renders properly', () => {
expect(toJson(gift)).toMatchSnapshot()
})
it('it initializes a person and present in `state`', () => {
expect(gift.state()).toEqual({ person: '', present: '' })
})
describe('when typing into the person input', () => {
const person = 'Uncle'
beforeEach(() => {
gift.find('.input-person').simulate('change', { target: { value: person } })
})
it('updates the person in `state`', () => {
expect(gift.state().person).toEqual('Uncle')
})
})
describe('when typing into the present input', () => {
const present = 'Golf Clubs'
beforeEach(() => {
gift.find('.input-present').simulate('change', { target: { value: present } })
})
it('updates the present in `state`', () => {
expect(gift.state().present).toEqual(present)
})
})
describe('when clicking the `remove gift` button', () => {
beforeEach(() => {
gift.find('.btn-remove').simulate('click')
})
it('calls the removeGift callback', () => {
expect(mockRemove).toHaveBeenCalledWith(id)
})
})
})
import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import Gift from '../components/Gift'
class App extends Component{
constructor() {
super()
this.state = { gifts: [] }
}
addGift = () => {
const { gifts } = this.state
const ids = this.state.gifts.map(gift => gift.id)
const max_id = ids.length > 0 ? Math.max(...ids) : 0
gifts.push({ id: max_id+1 })
this.setState({ gifts })
}
removeGift = (id) => {
const gifts = this.state.gifts.filter(gift => gift.id !== id)
this.setState({ gifts })
}
render() {
return(
<div>
<h2>Gift Giver</h2>
<div className="gift-list">
{
this.state.gifts.map(gift => {
return(
<Gift
key={gift.id}
gift={gift}
removeGift={this.removeGift}
/>
)
})
}
</div>
<Button
className="btn-add"
onClick={this.addGift}
>
Add Gift
</Button>
</div>
)
}
}
export default App
import React, { Component } from 'react'
import {
Form,
FormGroup,
FormControl,
FormLabel,
Button
} from 'react-bootstrap'
class Gift extends Component{
constructor() {
super()
this.state = {
person: '',
present: ''
}
}
render() {
return(
<div>
<Form>
<FormGroup>
<FormLabel>Person</FormLabel>
<FormControl
className="input-person"
onChange={(event) => this.setState({person: event.target.value})}
/>
</FormGroup>
<FormGroup>
<FormLabel>Present</FormLabel>
<FormControl
className="input-present"
onChange={(event) => this.setState({present: event.target.value})}
/>
</FormGroup>
</Form>
<Button
className="btn-remove"
onClick={() => this.props.removeGift(this.props.gift.id)}
>
Remove Gift
</Button>
</div>
)
}
}
export default Gift