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 1. Rubygems 1. 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
``` 1. There is [rate limiting](http://api.stackexchange.com/docs/throttle) for API calls 1. 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)
...    ``` 1. 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