058 RSpec

19 Apr 2014

RSpec is a testing tool for ruby programming language. In this episode, we will explore some common features of testing ushc as matchers, hooks, factories for initialising data and stubs.

Download video: mp4

Sample code: Github

Version: 2.14.8

Similar episodes: 023 Rails, 040 Sinatra

Background on RSpec

  1. Main website
  2. Rubygems
  3. rspec-core

Things to learn with RSpec

install

  1. install gem install rspec
  2. check its version rspec -v
  3. list total gems installed gem list rspec

1. first test and config

  1. write the test first - create a file playnum_spec.rb and write 2 pending tests

    describe '#calcube' do
      it 'returns a cube of a number'
    end
    
    describe '#calsquare' do
      it 'returns a square of a number'
    end
    
  2. run with some color formatting

    rspec playnum_spec.rb --color
    
  3. create a file .rspec and put the config options there instead

    --color
    --format doc
    

    and run the test rspec playnum_spec.rb

  4. add the default path for the test in the file .rspec and run just rspec

    --color
    --format doc
    --default_path spec
    
  5. fail the test as there's no implementation yet

    ...
    it 'returns a cube of a number' do
      c = Playnum.new
      expect(c.calcube(3)).to eq(27)
    end
    ...
    
  6. require the file to solve the error uninitialized constant Playnum in line 1 of file spec/playnum_spec.rb

    require './playnum'
    

    make a new file playnum.rb

    class Playnum
    
    end
    

    and run rspec

  7. to solve the error undefined method 'calcube' define the method

    class Playnum
      def calcube(num)
    
      end
    end
    
  8. to solve the error expected: 27 got: nil return just 27

    class Playnum
      def calcube(num)
            27
      end
    end
    
  9. test passes! add on another check in file spec/playnum_spec.rb for the cube of 2

    describe '#calcube' do
      it 'returns a cube of a number' do
            c = Playnum.new
            expect(c.calcube(3)).to eq(27)
            expect(c.calcube(2)).to eq(8)
      end
    end
    
  10. to solve expected: 8 got: 27, implement the entire method

    class Playnum
      def calcube(num)
        num * num * num
      end
    end
    
  11. implement the pending test

    describe '#calsquare' do
      it 'returns a square of a number' do
        c = Playnum.new
        expect(c.calcube(3)).to eq(27)
      expect(c.calcube(2)).to eq(8)
      end
    end
    

    and the method calcsquare

    class Playnum
      def calcube(num)
        num * num * num
      end
    
      def calsquare(num)
        num * num
      end
    end
    
  12. run only a section of the test indicating the line number

    rspec spec/playnum_spec.rb:11
    

2. hooks

  1. before and after hooks
  2. an example with before hook in file spec/playnum_spec.rb

    describe Playnum do
    
      before(:each) do
        @c = Playnum.new
      end
    
      describe '#calcube' do
        it 'returns a cube of a number' do
                expect(@c.calcube(3)).to eq(27)
                expect(@c.calcube(2)).to eq(8)
        end
      end
    
      describe '#calsquare' do
        it 'returns a square of a number' do
            expect(@c.calsquare(3)).to eq(9)
            expect(@c.calsquare(2)).to eq(4)
        end
      end
    
    end
    
  3. example of after hook

    before(:each) do
      @c = Playnum.new
      puts '*************'
    end
    
    after(:all) do
      puts 'Done!!'
    end
    

3. matchers

  1. try out other kinds of matchers

    expect(num).to be < 27
    expect(@c).to be_an_instance_of(Playnum)
    expect(@c).to be_kind_of(Playnum)
    
  2. try out other matchers e.g. shoulda matchers

  3. rspec using expect instead of should

4. factories

  1. create mock test data with factory girl
  2. create spec/user_spec.rb

    require './user'
    
    describe User do
    
      describe '#greeting' do
        it 'greets everyone' do
          u = User.new
          u.name = 'Anonymous'
          u.occupation = 'reader'
    
          expect(u.greeting).to eq('Hey Anonymous! What are you gonna read next?')
        end
    
        it 'greets an engineer' do
          u = User.new
          u.name = 'Limor Fried'
          u.occupation = 'engineer'
    
          expect(u.greeting).to eq('Ola Limor Fried! What hardware are you building currently?')
        end
    
        it 'greets an astronaut' do
          u = User.new
          u.name = 'Commander Hadfield'
          u.occupation = 'astronaut'
    
          expect(u.greeting).to eq('Hi Commander Hadfield! When are you next visiting the ISS?')
        end
      end
    
    end
    
  3. implement the User class in file user.rb

    class User
    attr_accessor :name, :occupation
    
      def greeting
        if @occupation == 'astronaut'
          'Hi ' + @name + '! When are you next visiting the ISS?'
        elsif @occupation == 'engineer'
          'Ola ' + @name + '! What hardware are you building currently?'
        else
          'Hey ' + @name + '! What are you gonna read next?'
        end
      end
    
    end
    
  4. integrate Factory Girls

    gem install factory_girl
    
  5. require the gem in file spec/user_spec.rb

    require './user'
    require 'factory_girl'
    
    FactoryGirl.find_definitions
    RSpec.configure do |config|
      config.include FactoryGirl::Syntax::Methods
    end
    
    describe User do
    ...
    
  6. create the factory file spec/factories.rb

    FactoryGirl.define do
      factory :user do
        name 'Anonymous'
        occupation 'reader'
      end
    end
    
  7. use the factory in spec/user_spec.rb

    it 'greets everyone' do
      u = build(:user)
      expect(u.greeting).to eq('Hey Anonymous! What are you gonna read next?')
    end
    
  8. create a child factory in file spec/factories.rb with inheritance

    FactoryGirl.define do
      factory :user do
        name 'Anonymous'
        occupation 'reader'
      end
    
      factory :astronaut, parent: :user do
        name 'Chris Hadfield'
        occupation 'astronaut'
      end
    
      factory :engineer, parent: :user do
        name 'Limor Fried'
        occupation 'engineer'
      end
    end
    
  9. use the child factory in file spec/user_spec.rb

    ...
    it 'greets an engineer' do
        u = build(:engineer)
        expect(u.greeting).to eq('Ola Limor Fried! What hardware are you building currently?')
    end
    
    it 'greets an astronaut' do
        u = build(:astronaut)
        expect(u.greeting).to eq('Hi Chris Hadfield! When are you next visiting the ISS?')
    end
    ...
    

5. stubs

  1. We will use the Stackexchange API with rubygem HTTParty
  2. create a file spec/question_spec.rb

    require './question'
    
    describe Question do
    
      describe '#count' do
        it 'gets the number of rspec questions from StackOverflow' do
          r = Question.new
    
          expect(r.count).to be > (10)
        end
      end
    
    end
    
  3. implement it in file question.rb

    require 'httparty'
    
    class Question
    
        def count
            response = HTTParty.get('https://api.stackexchange.com/2.2/questions?site=stackoverflow&tagged=rspec')
            titles = []
            JSON.parse(response.body)['items'].map { |arr| titles << arr['title'] }
    
            puts 'Current number of RSpec questions: ' + titles.count.to_s
            titles.count
        end
    
    end
    
  4. There is rate limiting for API calls

  5. stub the method

    ... 
    r = Question.new
    
    r.stub(:count).and_return(11)
    r.should_receive(:count).and_return(11)
    
    expect(r.count).to be > (10)
    ...
    
  6. run rspec in the command line and notice the puts statement is not executed

    rspec 
    

More Resources on RSpec and Testing

RSpec:

  1. WebMock
  2. Email Spec
  3. database cleaner
  4. shoulda matchers
  5. timecop
  6. Relishapp
  7. Betterspec
  8. Rails testing with RSpec

Testing frameworks in other languages

  1. SUnit for SmallTalk
  2. JUnit for Java
  3. PHPUnit for PHP
  4. TDD Web Development with Python
  5. Mocha, Jasmine for JavaScript

Build Link of this episode

Ruby Rogues hosted by Charles Max Wood