2 min read

Custom spec types with RSpec

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.