Simple Personal Rules to Structure Rspec Tests
RSpec is a popular testing framework for Ruby programs and it comes with its own helper methods and rules.
The mental checklist I have is to first write down the barebones structure of my tests. This has helped me plan out variables and the logic needed within my code.
- Split tests for methods using
describe
. - Within each describe block, scope out the different situations that could happen using
context
. - Determine how each context is different and break those out into variables using
let
. - Define and override the default let with the context specific data.
- Write out the expectations of the tests.
class MySimpleModel
def self.a_class_method
...
end
def an_instance_method
if external_service.my_conditional?
...
else
...
end
end
end
Using the above example, I can then write out the following specs based on the first couple steps of the checklist:
RSpec.describe MySimpleModel do
describe '.a_class_method' do
...
end
describe '#an_instance_method' do
context 'with external_service' do
context 'when my_conditional? is true' do
...
end
context 'when my_conditional? is false' do
...
end
end
end
end
I’ve broken out the class and instance method using the describe blocks. Then within my instance method’s test, I’ve also broken out the different situations that occur using contexts. In addition I’ve wrapped those context with another one explaining that there’s an external service in use.
RSpec.describe MySimpleModel do
...
describe '#an_instance_method' do
context 'with external_service' do
let(:external_service) do
instance_double('ExternalService', my_conditional?: my_conditional)
end
before do
allow(ExternalService).to receive(:new).and_return(external_service)
end
context 'when my_conditional? is true' do
let(:my_conditional) { true }
...
end
context 'when my_conditional? is false' do
let(:my_conditional) { false }
...
end
end
end
end
Now focusing on the instance test, I’ve added the variables that are shared (the external_service
double) and have defined the variables that are different in my contexts (the my_conditional
variable).
Now the stage is set to test the methods of this class and I can start to write out the expectations. Having fully scoped out the branches of logic and variables in use, it becomes clear what and how my code is being tested.
Alongside those steps in the checklist, I’ll reference BetterSpecs for other best practices when using RSpec. Happy testing!