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