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.
Top comments (2)
Nice writup on Rspec! I like to use describe/context for grouping tests and telling the story about what the class should and shouldn't do. Like you said, Rspec is all about making it human readable, so if you don't like how your test output reads, change it! Also helpful when you start getting into a bigger code base is to use shared examples with Rspec. So when you start pulling in concerns and modules that a class may use, you can easily set up spec that align with that concern but then test it in the class actually using the concern.
So for ex. if your
Coffee
wasextend Drinkable
in your rspec (coffee_spec.rb
) you could callit_behaves_like "drinkable"
which would call off to aRSpec.shared_examples "drinkable"
example that you could define.Exactly! I end up defaulting to RSpec for almost all my testing needs in real production code, because of that human readability, and all the great features inherent in it that help you write tests that make sense to you when you look back at them 2 months later, and forgot what you initially did. :)