DEV Community

loading...
Cover image for 49 Days of Ruby: Day 47 -- Testing Frameworks: RSpec

49 Days of Ruby: Day 47 -- Testing Frameworks: RSpec

Ben Greenberg
Rabbi turned Coder. Second Career Dev taking it one function at a time.
・3 min read

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (2)

Collapse
cseeman profile image
christine

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 was extend Drinkable in your rspec (coffee_spec.rb) you could call it_behaves_like "drinkable" which would call off to a RSpec.shared_examples "drinkable" example that you could define.

Collapse
bengreenberg profile image
Ben Greenberg Author

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. :)