What makes a good RSpec test?
RSpec is a testing framework written in Ruby to test Ruby code. To get started using RSpec with Rails, add it to the Gemfile.
group :development, :test do
gem 'rspec-rails'
end
Next finish setting it up by running bundle install in your project directory and then
rails generate rspec:install
It is easy to test for expected results, but how do you test for the unexpected? Good tests should include all edge cases, including best and worst-case scenarios; they should also be streamlined, very readable, and have redundancies eliminated.
Readability
Readability is as much about containment as it is eliminating obfuscation, intentional or otherwise.
Use contexts with scenarios to isolate possible edge cases.
describe '#package_status' do
context 'when package is delivered' do
# it should send notice to customer
# it should mark order as completed
end
context 'when package is pending' do
end
context 'when package is shipped' do
# it should send notice to customer
end
end
Keep the waters from getting muddied when testing different methods; prepend `.` when testing class and `#` when testing instance.
describe '#package_status' do
end
describe '.complete_orders' do
end
Ideally, you’ll keep each test as distinct and separate as possible so running down each one takes less work and makes for a more efficient process.
Don’t Repeat Yourself: DRY up your tests
When you find yourself writing the same setup for multiple tests, put it in a shared context. Again, efficiency is the name of the game. Keeping tests as focused as possible will save time and effort.
RSpec.shared_context 'pet_setup', shared_context: :metadata do
let(:cat) { FactoryBot.create(:cat, name: ‘Mittens’, age: 10) }
let(:dog) { FactoryBot.create(:dog, name: ‘Spot’, age: 2) }
let(:fish) { FactoryBot.create(:fish, name: ‘Nemo’, age: 100) }
end
Shared examples are beneficial when testing the behavior of different types.
shared_examples 'pet_attributes' do |name, age|
it 'returns its name' do
expect(subject.name) to eq(name)
end
it 'returns an age' do
expect(subject.age) to eq(age)
end
end
describe '#pet_behavior' do
include_context 'pet_setup'
context 'when pet is a dog' do
subject { dog }
it_behaves_like 'pet_attributes', 'Spot', 2
end
context 'when pet is a fish' do
subject { fish }
it_behaves_like 'pet_attributes', 'Nemo', 100
end
end
Speed up your Tests
Long test running times can be painful. If you’re keeping your tests separated and eliminating redundancies, you’ll want to zero in on any taking longer than they should.
To see your top 10 slowest tests, append --profile to your rspec command.
bundle exec rspec spec/models/dog_spec.rb --profile
In controller tests, you can reduce network requests by using before blocks and modifying the parameters in each context.
describe '#post' do
let(:user) { authorized_user }
let(:order_params) { user_id: user.id, cart: shopping_cart }
before do
post 'orders' params: order_params
end
context 'when user is authorized' do
it 'returns a successful response'
it 'returns a 200'
it 'fires off a notification'
end
context 'when user is not authorized' do
let(:user) { unauthorized_user }
It 'returns a flash message'
It 'redirects user to a sign up page'
end
end
Resources to learn more about using RSpec with Ruby
https://github.com/rspec/rspec-rails
https://pragprog.com/book/rspec3/effective-testing-with-rspec-3
Conclusion
Using techniques like what is listed above, we have had the opportunity to address clients’ concerns and they love it! If you are interested in joining our team, please visit our Careers page.