RSpec is a framework used in test-driven development (TDD) for writing feature and unit tests to evaluate whether a computer program is fit for purpose.
Test-Driven Development works in a red-green loop:
- Write the smallest possible test case that matches what we need to program.
- Run the test and watch it fail. This gets you into thinking how to write only the code that makes it pass.
- Write some code to make the test pass.
- Run your test suite. Repeat steps 2 and 3 until all tests pass.
- Go back and refactor your new code, making it as simple and clear as possible while keeping the test suite green.
The test always fails the first time because when we write the test, we haven’t attempted to pass it yet.
RSpec can be installed by navigating to the root project folder and typing:
$ gem install rspec
Feature & Unit Tests
Unit tests test individual parts of the logic of a program. When you write a unit test, it should be as small and simple as possible. A feature test tests the program as a user. e.g. using the interface a user would use to see if the feature works.
Guide To RSpec
By convention, tests written with RSpec are called “specs” (short for “specifications”) and are stored in the project’s
spec directory at root/spec/.
With RSpec, we are always describing the behaviour of classes, modules and their methods. The
describe block is always used at the top to put specs in a context. It can accept either a class name, in which case the class needs to exist, or any string you’d like.
describe ClassName do end
This is perhaps the simplest RSpec test. This test will simply test for the existence of ClassName.
If we run the test now, it will fail because we haven’t written any code yet. This is in fact what we expect, so it’s not a bad thing.
We will get a uninitialized constant error because we have asked the test to check for the existence of a constant called ClassName, but as we have no production code yet, this constant cannot possibly exist – because nothing exists yet.
All we have to do to get this test to pass is to initialise the constant by calling it in the production code (stored by convention in the lib folder):
class ClassName end
Before we run the test again, we need to alert Rspec of the existence of the file in which we’ve called the constant.
At the top of the spec file (above the test), add the line:
Note: the file extension (.rb) and the relative file path (root/lib/) are assumed by Rspec.
The test should now pass.
Let’s look at a slightly more complex example…
describe StringCalculator do describe ".add" do context "given an empty string" do it "returns zero" do expect(StringCalculator.add("")).to eq(0) end end end end
Notes about this example:
- We are using another
describeblock to describe the
addclass method. By convention, class methods are prefixed with a dot
(".add"), and instance methods with a dash
- We are using a
contextblock to describe the context under which the
addmethod is expected to return zero.
contextis technically the same as
describe, but is used in different places, to aid reading of the code.
- We are using an
itblock to describe a specific example, which is RSpec’s way to say “test case”. Generally, every example should be descriptive, and together with the context should form an understandable sentence. This one reads as “add class method: given an empty string, it returns zero“.
expect(...).toand the negative variant
expect(...).not_toare used to define expected outcomes. The Ruby expression they are given (in our case,
StringCalculator.add(""))is combined with a matcher to fully define an expectation on a piece of code. The matcher we are using here is
eq, a basic equality matcher. RSpec comes with many more built-in matchers.
RSpec Test Template
describe ClassName do describe "methodname" do expect(Classname.methodname($args)).to eq(result) end end
RSpec One-Liner Syntax
Rspec tests can also be written using one-liner syntax. There is surprisingly little literature about this online.
I need to figure this out.