Testing in any language is pretty much one of the most vital parts of development. Without it, applications become fragile, breaking whenever you try to add a new feature. So today we’ll take a look at the basics of RSpec and TDD (test-driven development).

Setting up

Let’s build a tiny standalone project:

mkdir rspec_tutorial
cd rspec_tutorial
bundle init
bundle add rspec
bundle exec rspec --init

That last command creates a spec/ folder and an .rspec file - the conventional layout you’ll see in every Ruby project.

Write the test first

We’re going to test a Dog class with a #bark method that returns "Woof!". Important: none of that code exists yet. In TDD we write the test first.

Create spec/dog_spec.rb:

require_relative "../dog"

RSpec.describe Dog do
  describe "#bark" do
    it 'returns the string "Woof!"' do
      expect(subject.bark).to eql("Woof!")
    end
  end
end

Breaking that down: RSpec.describe Dog scopes the tests to our class, the inner describe "#bark" scopes to the method, it describes the behaviour in plain English, and expect(...).to eql(...) is the actual assertion. subject is a freebie - RSpec instantiates Dog.new for you.

Run it:

bundle exec rspec

It fails, because there’s no Dog yet.

Make it pass

Create dog.rb in the project root - and let’s deliberately get it wrong first:

class Dog
  def bark
    "Meow!"
  end
end
expected: "Woof!"
     got: "Meow!"

A failing test that tells you exactly what’s wrong - that’s the whole point. Fix the string to "Woof!", run bundle exec rspec again, and enjoy the green dot.

In a Rails app

For Rails, use rspec-rails (version 8 as of writing):

# Gemfile
group :development, :test do
  gem "rspec-rails"
end
bundle install
bin/rails generate rspec:install

Same concepts, plus model specs, request specs and system specs to grow into. Red, green, refactor - repeat forever.