If you bear with me, I’ll eventually get to demonstrating how you can eliminate test teardown. Before we can dive into code I need to provide you a little background on why test teardown is traditionally an important thing and how as Rubyists we’ve moved away from most of our teardown needs.
I have a strong love of the four phase unit test. I was first exposed to the delicious structure of a well defined test in xUnit and passed this knowledge down to the younger members of my coding tribe in jUnit, test-unit, and eventually RSpec. RSpec was actually a revolutionary testing framework for me because I came to the realization that I didn’t actually need to care about all four phases of the well structured unit test. As it turns out, computers are really good at handling mundane tasks such as destroying objects you just created in the scope of a test and returning the system to a sane state.
I became a three phase unit tester. It was glorious. Unfortunately, someone hired me to start writing well-tested JavaScript code and I was thrown back into the world of caring about tearing down my mocks, stubs, and spies. This is not the world I want to live in. Let’s look at how I returned to the blissful world of the three phase unit test through code:
describe '#hasCareTeam', ->
beforeEach ->
@patient = new ICIS.Models.Patient
@hasDoctor = sinon.stub @patient, 'hasDoctor'
@hasHealthCoach = sinon.stub @patient, 'hasHealthCoach'
describe 'has a doctor', ->
beforeEach -> @hasDoctor.returns true
it 'returns true', ->
result = @patient.hasCareTeam()
expect(result).toBeTruthy()
describe 'does not have a doctor, has a health coach', ->
beforeEach ->
@hasDoctor.returns false
@hasHealthCoach.returns true
it 'returns false', ->
result = @patient.hasCareTeam()
expect(result).toBeFalsy()
afterEach ->
@hasDoctor.restore()
@hasHealthCoach.restore()
I think the structure and the readability of the test are going really well until
I hit that afterEach
block. My first reaction to working with sinon spies
was “Really? I have to restore them all?”. The answer was yes. If you do not
teardown your stubs/spies you’ll soon be in a world in which the order in which
your tests matter. You may not feel this pain the first time you skip
the restore
method, but eventually you’ll add a test that fails or behaves
in an entirely bizarre manner.
We toyed around with keeping an object in the testing namespace and then pushing
sinon objects in an array and in a global after block restore each of them. That
is until we did a source dive on sinon and found sinon.collection
. The
hard part of the implementation was done. You only need to perform two tasks:
First, add a global after hook (we did ours in the jasmine-sinon.js file)
afterEach ->
sinon.collection.restore()
Secondly, invoke mocks,stubs and spies with sinon.collection
describe '#hasCareTeam', ->
beforeEach ->
@patient = new ICIS.Models.Patient
@hasDoctor = sinon.collection.stub @patient, 'hasDoctor'
@hasHealthCoach = sinon.collection.stub @patient, 'hasHealthCoach'
describe 'has a doctor', ->
beforeEach -> @hasDoctor.returns true
it 'returns true', ->
result = @patient.hasCareTeam()
expect(result).toBeTruthy()
describe 'does not have a doctor, has a health coach', ->
beforeEach ->
@hasDoctor.returns false
@hasHealthCoach.returns true
it 'returns false', ->
result = @patient.hasCareTeam()
expect(result).toBeFalsy()
Voila! No more need to restore each of your test spies in Jasmine while maintaining the ability to make assertions of the desired state of a system after declaring test spies.