Custom spec types with RSpec
We use RSpec as our testing framework at Modern Message. And I learned something new about RSpec today.
You know those types that you can pass into RSpec specs? For example:
require "spec_helper"
RSpec.describe User, type: :model do
# Your specs here
end
Or
require "spec_helper"
RSpec.describe UsersController, type: :controller do
# Your specs here
end
Why am I writing about custom spec types?
At Modern Message, I'm currently working on a feature where I have to have common before
and after
hooks for multiple specs. To be specific, I'm currently using the stripe-ruby-mock library to stub out external requests made to Stripe in my tests.
For each test that utilizes the stripe-ruby-mock gem, we need a before
and after
hook that starts and stops the StripeMock
class. You can read through the docs in the GitHub page for the library, but essentially it looks something like this.
require "spec_helper"
RSpec.describe SomeClass do
before { StripeMock.start }
after { StripeMock.stop }
end
In my specific circumstance, I am writing tests for webhook events that Stripe sends out. For example, Stripe has a webhook event called invoice.payment_succeeded
. To create a corresponding model in my Rails app, I created a folder called stripe_service
in my models folder and a corresponding ruby class file app/models/stripe_service/invoice_payment_succeeded.rb
.
class StripeService::InvoicePaymentSucceeded
def call(event)
# Do stuff here with the webhook event.
end
end
Do note that Stripe does have multiple webhook events and in my specific case (and probably in most Rails applications), you would have multiple classes and files in the stripe_service
folder that represents different webhook events. And that means more spec files in spec/models/stripe_service/
folder with more repetitive before
and after
hooks starting and stopping the StripeMock
class.
While I'm one of those people who believe that there's nothing wrong with a little bit of duplication (especially in my case where we only have three webhook event classes), we can remove this before
and after
hook duplication with a custom spec type. Thus, if we create a custom spec type, our Stripe::InvoicePaymentSucceeded
spec will look like this.
require "spec_helper"
RSpec.describe StripeService::InvoiceSucceeded, type: :stripe_service do
# Your specs here without the before and after hooks
# starting and stopping the StripeMock class
end
To create this custom stripe_service
type, we need to go into our spec/spec_helper.rb
file and define this new type. Here's an example.
RSpec.configure do |config|
# Bunch of other configurations
config.define_derived_metadata(file_path: Regexp.new('/spec/models/stripe_service/')) do |metadata|
metadata[:type] = :stripe_service
end
config.before(:each, type: :stripe_service) { StripeMock.start }
config.after(:each, type: :stripe_service) { StripeMock.stop }
# Bunch more other configurations
end
Here, the first thing we do is that we specify that all of the specs in the spec/models/stripe_service
folder to have the type stripe_service
. And in the following lines of the configuration, we use our new strip_service metadata type, we specify that before
and after
hooks both start and stop the StripeMock
classes. With this configuration, each time we specify our specs to have the type stripe_service
, it'll automatically run the before
and after
hooks automatically, reducing duplication in our specs.