First we write a test.
Then we run it and make sure it fails as expected.
Only then do we write some code.
We try to be smart and we work really hard, so we often succeed.
Imagine programming is like pulling up a bucket of water from a well. When the well isn't too deep and the bucket isn't too full, it's easy. After a while, though, you're going to get tired.
TDD is like having a ratchet that lets you save your progress, take a break, and make sure you never slip backwards. That way you don’t have to be smart all the time.
When you write your tests first, you tend to break your code into smaller chunks with well-defined inputs and outputs.
These smaller chunks are much easier to reuse and compose later.
When writing your tests first, it's easier to:
With great test coverage in place, you can be confident when you need to come back later and make big changes. You'll also be providing a great resource to other developers who maintain your code later.
Tests that evaluate small pieces of functionality by evaluating the result given a certain input or state.
The code is reasonably isolated from other functionality, with external modules often replaced with mock objects so they don't affect the outcome.
Tests that describe how a user interacts with your application and check the results based on what the user should see.
All of the components of your app are tested together, fully integrated.
This is usually accomplished through something like Selenium.
"Futuristic" runner written by Sindre Sorhus for super speed via multi-process parallel testing.
Created by Pivotal Labs for Ruby/Rails integration.
Created by TJ Holowaychuk to be very flexible and easy to use for Node developers.
Written by substack to be lightweight, global-free, and very modular.
The following examples use Mocha, but the concepts can generally be applied across all runners.
describe('todoService', () => {
before(() => {
// Setup code that runs before the test suite is executed
})
describe('getTodos()', () => {
before(() => {
// Setup code that runs before the getTodos() tests are executed
})
it('retrieves all todos from the API', () => {
const result = getTodos()
expect(result).to.deep.equal({id: 1, title: 'Write tests'})
})
})
})
before(() => {
// Setup code that runs before the tests within the current describe block
})
beforeEach(() => {
// Setup code that runs before each test
})
after(() => {
// Teardown code that runs after the tests within the current describe block
})
afterEach(() => {
// Teardown code that runs after each test
})
it('retrieves all todos from the API', done => {
getTodos(result => {
expect(result).to.deep.equal({id: 1, title: 'Write tests'})
done()
})
})
it('retrieves all todos from the API', () => {
const promise = getTodos()
return promise.then(result => {
expect(result).to.deep.equal({id: 1, title: 'Write tests'})
})
})
it('retrieves all todos from the API', () => {
const promise = getTodos()
return expect(result).to.eventually.deep.equal({id: 1, title: 'Write tests'})
})
import chai from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai' ;
chai.use('sinon-chai')
describe('myModule', () => {
describe('myFunction()', () => {
it('calls the given callback with an array', () => {
const callback = sinon.spy()
myFunction(callback)
expect(callback).to.have.been.calledOnce
const call = callback.firstCall
expect(call.args[0]).to.be.an.instanceof(Array)
})
})
})
import nock from 'nock' ;
const platform = nock('http://localhost:8080') ;
describe('getPostTitles()', () => {
it('requests posts from the platform and returns just the titles', () => {
platform.get('/posts').reply(200, {id: 1, title: 'My Post', author: 'Me'})
const results = getPostTitles()
expect(results).to.equal(['My Post'])
})
})
import {isResponsive} from '../path/to/codingTools' ;
export function myFunction() {
// Code all the things!
}
const isResponsiveStub = sinon.stub().returns(true)
const {myFunction} = proxyquire('../src/myModule', {
'../path/to/codingTools': {isResponsive: isResponsiveStub}
})
// Test all the things!
myModule.js →
testMyModule.js →
What if you need isResponsive() to return true every time it's called?
Identify discrete units of functionality and write tests that only test one thing.
For every function, think through the possible inputs and expected outputs. Test each case individually.
If you find yourself mocking or injecting a lot, you may want to refactor.
TDD is a discipline, and that means it’s not something that comes naturally; because many of the payoffs aren’t immediate but only come in the longer term, you have to force yourself to do it in the moment.
Like a kata in a martial art, the idea is to learn the motions in a controlled context, when there is no adversity, so that the techiques are part of your muscle memory.
The danger is that complexity tends to sneak up on you, gradually.
Test action creators to ensure they return the right actions.
Test reducers to make sure the state is appropriately modified.
Test event handlers on components to verify that they are correctly handled.
(Use Function.call() to attach test props.)
Don't test propType validation - React already covers that functionality in their test suite.
import {shallow} from 'enzyme'
import React from 'react'
import sinon from 'sinon'
describe('<MyComponent />', () => {
it('renders children when passed in', () => {
const wrapper = shallow(
<MyComponent>
<div className="unique" />
</MyComponent>
)
expect(wrapper.contains(<div className="unique" />)).to.equal(true)
});
it('simulates click events', () => {
const onButtonClick = sinon.spy()
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
)
wrapper.find('button').simulate('click')
expect(onButtonClick.calledOnce).to.equal(true)
})
})
It's been a long journey, but effective JSX unit testing is finally here thanks to airbnb's Enzyme library.