Testing with RSpec: Part I – Basic concepts
Introduction
Hello, fellow reader! Let’s explore the fundamental concepts for the Ruby gem RSpec
together. We’ll cover its basic structure, progressing from a comprehensive to a complete test case. By the end, the basic knowledge of RSpec features will have been explained, and how to use them. Now let’s dive into RSpec features.
RSpec
RSpec is a ruby gem, composed of multiple gems that work together:
For this first post, we’re not gonna cover rspec-mocks
and rspec-rails
. This will be a part II or III.
describe
describe
is a method that accepts in most cases a string or a class as argument. Often used to describe an object and/or an action. So we can use the describe
method to organize our test file in multiple blocks, normally one for each message that an object can receive.
RSpec.describe Greeter do
describe '#hello' do
end
end
In this simple example, we are describing how the class Greeter
and the instance method #hello
work. Let’s keep going, we have more to cover.
context
Similar to describe
, context
is a method provided by RSpec
and, as its name suggests, is what we can use to give specific scenarios (contexts) for a use case. By using context
we can keep our tests clean and organized.
With context
, it is possible to give a description of the different behaviors that a method might have. When writing the description, a good tip is to use the words "when", "with" or "without" at the beginning (see betterspecs). Let’s continue our previous example.
RSpec.describe Greeter do
describe '#hello' do
context 'when a name is given' do
end
context 'when the name is nil' do
end
end
end
Great! Now we have at least two different behaviors for Greeter#hello
. But what happens in each context? This is what we’re going to find out next.
it
it
is the last part of a test structure, here is where it can be written the actual result for each context.
RSpec.describe Greeter do
describe '#hello' do
context 'when a name is given' do
it 'returns a greeting message with the given name' do
end
end
context 'when the name is nil' do
it 'returns a greeting message without a name' do
end
end
end
end
At this point, we can imagine that Greeter#hello
returns a message that might have a name, if provided.
- TIP: Note that we build our test structure by nesting
describe
,context
, andit
, which is good but can become hard to read if we nest too manycontexts
. There is no specific number of contexts but it is good to use a limit to keep the codebase organized and use this limit as a warning. This way, if the nesting is increasing, it might be an indicator that the object/method under test is complex and would be good thing to refactor it.
Ok, we have the structure, but now what? How do we test the object? This is where some useful RSpec
methods take place. First, we’re gonna cover subject
.
subject
As mentioned before, subject
is another method that can be used to describe the object under test in multiple examples. Here’s how it can be used.
RSpec.describe Greeter do
subject(:greeter) { described_class.new(name) }
describe '#hello' do
context 'when a name is given' do
it 'returns a greeting message with the given name' do
end
end
context 'when the name is nil' do
it 'returns a greeting message without a name' do
end
end
end
end
— OK, what happened? Why is it called :greeter
? What is described_class
? name
? What?
No worries, let’s cover it step by step. First, :greeter
is a symbol that can be used to give a name to the subject
, this is not required but might help the test readability. Second, described_class
returns the class provided on the first describe
. In this example Greeter
is the same as subject(:greeter) { Greeter.new(name) }
. And what about name
? This is where the let
method takes place. Let’s take a look.
let
Same as before, let
is a method that will define another one. We can think of it as creating a variable that can be referenced throughout the test. When using let
we have a lazy loading, in which the method defined by it will only be created when it is called for the first time. There is a variant for let
which is let!
, and the main difference is that let!
is defined while defining a block. It is usually used to seed the test database previously before the test runs, so we don’t need to call it to create/instantiate the object defined. Let’s see how we can use let
for Greeter#hello
.
RSpec.describe Greeter do
subject(:greeter) { described_class.new(name) }
describe '#hello' do
context 'when a name is given' do
let(:name) { 'Tony' }
it 'returns a greeting message with the given name' do
end
end
context 'when the name is nil' do
let(:name) { nil }
it 'returns a greeting message without a name' do
end
end
end
end
Now that we have defined name
, the greeter
subject will know which value to use in each context. While defining a method with let
, it can be possible to easily lose track of definitions for different contexts which can lead to misinterpretation of the test. So a good tip is to avoid when possible global let
like a subject
.
Ok, this is good and all but where is the expectation? Let’s wrap it up now.
expect
As the name suggests, it is a method used to do the test assertions for an it
block. The expect
method can be used to assert certain behaviors, including errors. Now that we have everything done, let’s complete our test.
RSpec.describe Greeter do
subject(:greeter) { described_class.new(name) }
describe '#hello' do
context 'when a name is given' do
let(:name) { 'Tony' }
it 'returns a greeting message with the given name' do
expect(greeter.hello).to eq('Hi, my name is Tony.')
end
end
context 'when the name is nil' do
let(:name) { nil }
it 'returns a greeting message without a name' do
expect(greeter.hello).to eq('Hi, my name is .')
end
end
end
end
We covered the basics of RSpec
testing, from its fundamental structure to a complete test case. We are going to dive into more details in the next posts, especially Ruby on Rails tests for models and request specs. So stay tuned: there will be more to cover in the next posts. See you soon.
We want to work with you. Check out our "What We Do" section!