Welcome to day 47 of the 49 Days of Ruby! 🎉
Yesterday, we explored the testing library Minitest as we looked at testing implementations in Ruby for the first time.
Today, we are going to explore RSpec as another testing framework option. RSpec has a lot of "bells and whistles", which can be overwhelming, and you may not need all of them all the time. However, when you do need one of those bells or whistles, you will appreciate the benefits of it being packaged together.
RSpec defines itself as:
Behavior driven development for Ruby
Essentially, RSpec when done within a context of TDD (Test Driven Development), trains the developer to think about the kind of results they want their code to accomplish. It is done using maximally human readable language.
For example, this is what a test in RSpec might look like, taken from the RSpec website:
RSpec.describe Bowling, "#score" do
context "with no strikes or spares" do
it "sums the pin count for each roll" do
bowling = Bowling.new
20.times { bowling.hit(4) }
expect(bowling.score).to eq 80
end
end
end
Do you notice how verbose that example is? There are keywords like describe
, context
, and it
. All those statements help form a clear human sentence when the test is run:
$ bin/rspec
Bowling#score
with no strikes or spares
sums the pin count for each roll
Finished in 0.00137 seconds (files took 0.13421 seconds to load)
1 example, 0 failures
You do not need to have the context
block, but it helps add another layer of comprehensibility to a test when running it.
RSpec Basics
Let's cover a little bit of the basic functionality of RSpec. I encourage you to spend some time today familiarizing yourself more with it and see what kind of tests you can build!
Imagine we had some code in a file called my_code.rb
and we wanted to test it.
First, we would require
that file in our test file. The convention is to name the test file the same name as the code file and append _spec
to it: my_code_spec.rb
:
# my_code_spec.rb
require "my_code"
Now, let's continue our imagined example and say we had a class called Coffee
inside our my_code
file:
# my_code_spec.rb
require "my_code"
RSpec.describe Coffee do
end
Then, now extending it, we also have a class method called #name
:
# my_code_spec.rb
require "my_code"
RSpec.describe Coffee do
context "when asked for a coffee name" do
it "returns the correct name" do
expect(Coffee.name).to eq("Espresso")
end
end
end
That is a pretty straightforward test. We want to confirm that when the #name
method is invoked that it outputs the string Espresso
.
What if our Coffee
class was a bit more complex and had a lot of parameters to initialize with? Would we have to make a new instance for each test? The subject
convention can help make that less repetitive. Perhaps the #name
method is actually an instance method:
# my_code_spec.rb
require "my_code"
RSpec.describe Coffee do
let(:subject) do
Coffee.new(
something: "its value",
something_else: "its value",
another_thing: 123456,
yet_another_thing: true,
coffee_type: "Espresso"
)
end
describe "#name" do
it "returns the correct coffee name" do
expect(subject.name).to eq("Espresso")
end
end
end
We can continue to reuse that subject
variable, which holds an instance of Coffee
for all of our relevant tests. It can save us a lot of repetitive typing!
For the rest of the day continue exploring RSpec. Try to write some tests that define your future code's expectations, and then write the code to help those tests pass.
See you tomorrow!
Come back tomorrow for the next installment of 49 Days of Ruby! You can join the conversation on Twitter with the hashtag #49daysofruby.