Here is where I post things

Laziness. Impatience. Hubris.

Writing RSpec Tests for Dummies

This week I struggled with conceptualizing and expressing useful tests in RSpec. After getting past the basic DSL of RSpec, describe and it blocks, I would find myself lost about how to express my expectations.

My first instinct when I’m struggling with a concept is to read all the material I can find online and do any tutorials I can get my hands on, but everything I found on RSpec referred to the deprecated “should” syntax. I didn’t want to further confuse the issue in my head by reading materials that demonstrated a deprecated syntax. So I embarked on a trial-and-error process of building a simple class with some reasonably thorough tests.

I found a few resources and tricks that helped me get a handle on the subject.

Tests are just expect() paired with a “matcher”

Behaviour is asserted by pairing expect().to and expect().not_to with a Matcher predicate.

expect(a_result).to eq("this value")
expect(a_result).not_to eq("that value")

We have typically used the eq() method as a matcher, but it’s useful to know the other slightly different equality measures.

eq(expected) # same value
eql(expected) # same value and type
equal(expected) # same exact object

Basic matchers cheatsheet: https://learn.thoughtbot.com/test-driven-rails-resources/matchers.pdf

More matchers in RSpec docs: http://rubydoc.info/gems/rspec-expectations/file/README.md#Built-in_matchers

Fun with RSpec

If I see another artist or movie spec, I might jump out a window, so I decided to build a really simple little model of something I enjoy, gin.

class Gin
  attr_accessor :name, :style, :notes
  GINS = []
  def initialize
    GINS << self
  end
  def self.all
    GINS
  end
  def self.reset_all
    GINS.clear
  end
end

And now to the fun part, writing the tests.

  describe 'Gin Attributes' do

    let(:tanqueray) { Gin.new }

    it 'can have a name' do
      tanqueray.name = "Tanqueray"
      expect(tanqueray.name).to eq("Tanqueray")
    end

    it 'can have a style' do
      tanqueray.style = "London Dry"
      expect(tanqueray.style).to eq("London Dry")
      expect(tanqueray.style).to match(/London Dry/)
    end

    it 'can have multiple notes of flavor' do
      notes = ["angelica root","liquorice","juniper","coriander"]

      tanqueray.notes = ["juniper","coriander","angelica root","liquorice"]
      expect(tanqueray.notes.length).to eq(4)
      expect(tanqueray.notes).to match_array(notes)
      expect(tanqueray.notes).to have(4).notes
    end

  end

match() is great for when you need a regex. I also found match_array() useful. I especially like the readability of expect(tanqueray.notes).to have(4).notes. The addition of .notes at the end is not required and is pure sugar.

From there, I moved on to a new describe block to spec out my class methods. In addition to the standard eq(), I used a comparison operator, which is also supported.

  describe 'Gin class methods' do

    before(:each) do
      Gin.all.clear
    end

    it 'can list all Gins' do

      hendricks = Gin.new
      expect(Gin.all.length).to eq(1)

      barr_hill = Gin.new
      expect(Gin.all.count).to be > 1
    end

    it 'can reset the list of Gins' do
      gins = [Gin.new, Gin.new]
      Gin.reset_all
      expect(Gin.all.length).to eq(Gin.all.clear.length)
    end

  end

I didn’t get as far as I had intended with these tests, so I hope to follow this up with a further exploration of test set up and tear down. I had a tough time getting that to work elegantly so think I still have much to learn there.

Satisfy and custom matchers

If the standard matchers don’t work well for a given scenario, you can also use satisfy to get a little more manual with it, and RSpec also allows for custom matchers to be defined.

satisfy is valid for objects and blocks, and allows the target to be tested against an arbitrarily speciļ¬ed block of code.

    it 'can have multiple notes of flavor' do
      expect(tanqueray.notes).to satisfy {|n| n.count == 4}
    end

Other points I made note of along the way:

Add --format documentation to your .rspec file after you run rspec --init; this is helpful for the test writing process.

This was the most useful cheatsheet I found and is referenced throughout this article. https://www.anchor.com.au/wp-content/uploads/rspec_cheatsheet_attributed.pdf