Written by: steve ross on March 28th 2011

This will teach me to write and get all philosophical. I just deleted two paragraphs of stuff you already know so I can get to what I really wanted to say:

If you are using Jasmine and you are not writing custom matchers, you may not be getting all you can from your tests.

For this post, I will be using Coffeescript. No matter, it's Javascript with some keen decoration. Easy on the Rubyists or the Pythonista's eyes. So take the problem: I have a page where if I click a link, it slides a drawer down (a div, actually) with some input boxes. And if I click the link again, it slides it back up. I'm using jQuery for this, but how to test and make it readable.

First try: Spell out the selectors individually

describe 'UI', () ->
  describe 'Lightbox Drawer', () ->
    beforeEach ->
      @lightbox_number = $('#lightbox-number')
      @lightbox_button = $('li#lightbox-link a')
      jasmine.Clock.useMock()  # <== Very cool way to trick the settimer and setinterval

    it 'is initially hidden', () ->
      expect(@lightbox_number.is(':visible')).toBe(false)

    it 'should be revealed when "lightbox" is first clicked', () ->
      $('#lightbox-entry').hide()  # set initial state to hidden
      @lightbox_button.click()
      jasmine.Clock.tick(500)
      expect($('#lightbox-entry').is(':visible')).toBe(false)

You get the point, but the spec above is wrong. It contains an erroneous assumption that the English Language phrase "is visible" translates to true or false. That's not what .is(':visible') does at all. It returns the visible elements inside the selector previous, so it will not often evaluate to true or false, and if it does, it's purely by coincidence.

Second try: Correct the bug

describe 'UI', () ->
  describe 'Lightbox Drawer', () ->
    beforeEach ->
      @lightbox_number = $('#lightbox-number')
      @lightbox_button = $('li#lightbox-link a')
      jasmine.Clock.useMock()  # <== Very cool way to trick the settimer and setinterval

    it 'is initially hidden', () ->
      expect(@lightbox_number.is(':visible')).toBe('undefined')

    it 'should be revealed when "lightbox" is first clicked', () ->
      $('#lightbox-entry').hide()  # set initial state to hidden
      @lightbox_button.click()
      jasmine.Clock.tick(500)
      expect($('#lightbox-entry').is(':hidden')).toBe('undefined')

Ok, this is better, because the result of a jQuery selector that finds nothing is undefined.

Many other iterations, but then... Custom Matchers!

describe 'UI', () ->
  describe 'Lightbox Drawer', () ->
    beforeEach ->
      @lightbox_number = $('#lightbox-number')
      @lightbox_button = $('li#lightbox-link a')
      jasmine.Clock.useMock()  # <== Very cool way to trick the settimer and setinterval

    it 'is initially hidden', () ->
      expect(@lightbox_number).toBeHidden()

    it 'should be revealed when "lightbox" is first clicked', () ->
      $('#lightbox-entry').hide()  # set initial state to hidden
      @lightbox_button.click()
      jasmine.Clock.tick(500)
      expect($('#lightbox-entry')).toBeVisible()

    it 'should be hidden when the lightbox tray is showing and the lightbox link is clicked', () ->
      $('#lightbox-entry').show()  # set initial state to showing
      expect($('#lightbox-entry')).toBeVisible()
      @lightbox_button.click()