Join our team!

We're looking for mid-level and senior software developers who love working throughout the stack, and have a track record for designing, building, shipping, and supporting web services and applications. Ideal candidates love Ruby, Rails and Ember, and using BDD and agile processes to ship working software quickly! Experience with Redis, Postgres, RabbitMQ, and ElasticSearch are a huge plus!

Learn more about our mission by visiting IoraHealth.com and by following @IoraHealth.

Eliminating Mock, Stub, and Spy Teardown with Sinon Collection Methods

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.