Old Guy New Trick

An old guys journey to learn how to code.

Back to Basics with RSpec (Part 2)


Author: John on April 23, 2016

In the previous blog post (posting) we discussed how to start a basic ruby project using RSpec for testing.  We confirmed that RSpec was working and created a simple test using a double.  In today's blog posting we will test drive out an actual class for our robots.

If you have been following along since the previous post, go ahead and fire up your favorite editor and open spec/robot_spec.rb.  Add a new test like follows:

describe "Creating an instance of a robot" do
  it "returns an instance of a Robot" do
    tobor = Robot.new
    expect(tobor).to be_a Robot
  end
end

Now re-run your test using the rspec command:

➜  robot_testing git:(master) ✗ rspec

A robot double
  returns canned response from the methods named in the provided hash

Creating an instance of a robot
  returns an instance of a Robot (FAILED - 1)

Failures:

  1) Creating an instance of a robot returns an instance of a Robot
     Failure/Error: tobor = Robot.new

     NameError:
       uninitialized constant Robot
     # ./spec/robot_spec.rb:13:in `block (2 levels) in '

Finished in 0.00117 seconds (files took 0.08719 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/robot_spec.rb:12 # Creating an instance of a robot returns an instance of a Robot

➜  robot_testing git:(master) ✗

Ok, now have a failing test.  Take a close look at the error.

NameError:
  uninitialized constant Robot

When you see an error with 'uninitialized constant' this usually means the expected class is missing.  So let's see if we can remedy this issue by creating a robot class.  In the root of our project directory, create and edit a file.  For example:

touch robot.rb
vim robot.rb

class Robot
end

Save the file and re-run your test.  Did you get the same error?  No problem we will fix that.  Before we do so, just as a refresher, your project directory and file structure should like similar to the following:

robot_testing
├── Gemfile
├── Gemfile.lock
├── README.md
├── robot.rb
└── spec
    ├── robot_spec.rb
    └── spec_helper.rb

1 directory, 6 files

Ok, now that we have the robot.rb file, we need to tell RSpec about it.  Edit your spec/robot_spec.rb file and add the following:

require './robot'

Try your test again, and you should be green for both tests:

➜  robot_testing git:(master) ✗ rspec

A robot double
  returns canned response from the methods named in the provided hash

Creating an instance of a robot
  returns an instance of a Robot

Finished in 0.00145 seconds (files took 0.08097 seconds to load)
2 examples, 0 failures

➜  robot_testing git:(master) ✗

Excellent, we are making good progress.  What good is a robot without a name and a model?  One of my favorite robots as a kid was Twiki from Buck Rogers.  So, let's create a new test in our robot_spec.rb file that expects a new robot to accept a name and model as part of the initialization.

describe "Creating a robot with name and model" do
  it "returns a valid robot with name and model" do
    tobor = Robot.new('Twiki', 'ambuquad')
    expect(tobor.name).to eq('Twiki')
    expect(tobor.model).to eq('ambuquad')
  end
end

Save the file after your changes, and run your test:

➜  robot_testing git:(master) ✗ rspec

A robot double
  returns canned response from the methods named in the provided hash

Creating an instance of a robot
  returns an instance of a Robot

Creating an instance of a robot
  returns a valid robot with name and model (FAILED - 1)

Failures:

  1) Creating an instance of a robot returns a valid robot with name and model
     Failure/Error: tobor = Robot.new('Twiki', 'ambuquad')

     ArgumentError:
       wrong number of arguments (given 2, expected 0)
     # ./spec/robot_spec.rb:21:in `initialize'
     # ./spec/robot_spec.rb:21:in `new'
     # ./spec/robot_spec.rb:21:in `block (2 levels) in '

Finished in 0.00186 seconds (files took 0.07848 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/robot_spec.rb:20 # Creating an instance of a robot returns a valid robot with name and model

➜  robot_testing git:(master) ✗

Ok, another failing test.  This is ok.  Review the error message, paying attention to:

ArgumentError:
  wrong number of arguments (given 2, expected 0)

What do you think the problem is?  Remember in Ruby that all of your created classes inherit from Object.  And we get a default initialize (new) method but that default expects zero arguments.  So we need to add our own initialize method in our robot.rb file.

 

Tip:

# using irb or pry:
class Robot
end

r = Robot.new
r.class.ancestors
=> [Robot, Object, PP::ObjectMixin, Kernel, BasicObject]

If it is not already open, re-open your robot.rb.  Add an initialize method like the example below:

class Robot
  def initialize(name, model)

  end
end

Guess what you should do next after saving the file.  Yup, re-run your test.

➜  robot_testing git:(master) ✗ rspec

A robot double
  returns canned response from the methods named in the provided hash

Creating an instance of a robot
  returns an instance of a Robot (FAILED - 1)

Creating an instance of a robot
  returns a valid robot with name and model (FAILED - 2)

Failures:

  1) Creating an instance of a robot returns an instance of a Robot
     Failure/Error: tobor = Robot.new

     ArgumentError:
       wrong number of arguments (given 0, expected 2)
     # ./robot.rb:2:in `initialize'
     # ./spec/robot_spec.rb:14:in `new'
     # ./spec/robot_spec.rb:14:in `block (2 levels) in '

  2) Creating an instance of a robot returns a valid robot with name and model
     Failure/Error: expect(tobor.name).to eq('Twiki')

     NoMethodError:
       undefined method `name' for #
     # ./spec/robot_spec.rb:22:in `block (2 levels) in '

Finished in 0.00147 seconds (files took 0.0836 seconds to load)
3 examples, 2 failures

Failed examples:

rspec ./spec/robot_spec.rb:13 # Creating an instance of a robot returns an instance of a Robot
rspec ./spec/robot_spec.rb:20 # Creating an instance of a robot returns a valid robot with name and model

➜  robot_testing git:(master) ✗

What the heck?  We broke a test that was working!  I'm going to go grab some coffee, see if you can get either of those failing tests to work on your own.  I'll be back in a few minutes to see how you are doing.

tick, tick, tick, tick......

How did you do?  I did a small edit and I got the second test to pass.  Here is what I did:

describe "Creating an instance of a robot" do
  it "returns an instance of a Robot" do
    tobor = Robot.new('Twiki', 'ambuquad')
    expect(tobor).to be_a Robot
  end
end

When we made the change to robot.rb and added arguments to the initializer, we broke the second test.  So I updated the second test so that it now has the two required arguments.  I re-ran the test and now just the third is failing.

Failures:

  1) Creating an instance of a robot returns a valid robot with name and model
     Failure/Error: expect(tobor.name).to eq('Twiki')

     NoMethodError:
       undefined method `name' for #
     # ./spec/robot_spec.rb:22:in `block (2 levels) in '

Finished in 0.00158 seconds (files took 0.07851 seconds to load)
3 examples, 1 failure

We will wrap up todays posting by solving this issue and making all three of our tests go green.  Time to re-edit our robot.rb file:

class Robot
  attr_reader :name, :model

  def initialize(name, model)
    @name = name
    @model = model
  end
end

Save the file and re-run your tests.  Do you understand the changes we made?  We created instance variables for name and model that are passed in when we create (instantiate) a new robot.  Is the attr line new to you?  In the next posting we will review this last step and take a few minutes to talk about attr_reader.

➜  robot_testing git:(master) ✗ rspec

A robot double
  returns canned response from the methods named in the provided hash

Creating an instance of a robot
  returns an instance of a Robot

Creating an instance of a robot
  returns a valid robot with name and model

Finished in 0.00154 seconds (files took 0.08623 seconds to load)
3 examples, 0 failures

➜  robot_testing git:(master) ✗

AWESOME JOB!  With all of our tests green, we can call this a wrap.  You can clone the repo here

Learn Something New Everyday

Last Edited by: John on May 04, 2016