🗂

Remove stripe-ruby-mock gem

2023/12/23に公開

This post is a part of YAMAP Engineers Advent Calendar 2023.
https://qiita.com/advent-calendar/2023/yamap-engineers

Introduction

Stripe is an excellent payment service with very nice developer experience. They provide extensive support for testing their services in test mode. However, like other third-party services, automated testing can be a bit challenging.

A good option is stripe-ruby-mock gem. Simple add the gem, and you're good to go - it works out-of-the box, making test writing a breeze.
However, it does come with a couple of drawbacks:

  • It locks the Stripe gem version, limiting your ability to keep your production gem version up to date, which doesn't feel right for a test gem.
  • It mocks the Stripe server, but given Stripe's frequent updates, there's a risk of not testing against the latest Stripe version.

In this post, I'd like to suggest an alternative for that gem: replacing it with VCR for Stripe API requests, and JSON files for webhook events.

Using VCR to mock requests to the Stripe server

For tests that send API requests to Stripe, StripeMock starts a server to listen to those requests. They've implemented various request handlers, such as this PaymentIntent request handler.
I think it's a great work of them to try to mimic the input and output of Stripe server for all those requests. However, a mock server and a real server always have the risk of difference.
To address this, we can use VCR to let our tests make requests to the real Stripe server, recording the requests and responses for later test run.

Code using StripeMock:

require 'stripe_mock'

describe MyApp do
  let(:stripe_helper) { StripeMock.create_test_helper }
  before { StripeMock.start }
  after { StripeMock.stop }

  it "creates a stripe customer" do
    customer = Stripe::Customer.create({
      email: 'johnny@appleseed.com',
      source: stripe_helper.generate_card_token
    })
    expect(customer.email).to eq('johnny@appleseed.com')
  end
end

Code using VCR:

describe MyApp do
  it "creates a stripe customer", :vcr do
    customer = Stripe::Customer.create({
      email: 'johnny@appleseed.com',
      source: 'tok_visa'
    })
    expect(customer.email).to eq('johnny@appleseed.com')
  end
end

I use the test-mode API key in the test environment to make API requests on local. These requests are recorded in VCR cassettes, committed to git, and used to run tests in the CI environment. To avoid creating unnecessary data in your test mode, consider creating a separate Stripe account for automated test requests.

Using event JSON from the Stripe Dashboard to mock webhook requests

StripeMock can also mock webhook events

it "mocks a stripe webhook" do
  event = StripeMock.mock_webhook_event('customer.created')

  customer_object = event.data.object
  expect(customer_object.id).to_not be_nil
  expect(customer_object.default_card).to_not be_nil
  # etc.
end

Under the hood, it creates a Stripe::Event object from JSON files, merging them with customized attributes.
The code is straightforward enough to maintain ourselves. Move it to a helper method in your codebase, and use your own JSON files.

# spec/support/stripe.rb
module StripeHelper
  WEBHOOK_FIXTURE_PATH = Rails.root.join('spec/fixtures/stripe_webhooks').to_s

  def mock_webhook_event(type, params = {})
    fixture_path = File.join(WEBHOOK_FIXTURE_PATH, "#{type}.json")
    unless File.exist?(fixture_path)
      raise ArgumentError.new("No fixture file found for webhook event `#{type}`.")
    end

    event_data = JSON.parse(File.read(fixture_path))

    event_data = ::Stripe::Util.symbolize_names(event_data)
    params = ::Stripe::Util.symbolize_names(params)

    event_data[:account] = params.delete(:account) if params.key?(:account)
    event_data[:data][:object] = rmerge(event_data[:data][:object], params)
    event_data[:created] = params[:created] || Time.current.to_i
    event_data[:id] = "evt_#{SecureRandom.alphanumeric}"

    ::Stripe::Event.construct_from(event_data)
  end
end

You can easily acquired the events' JSON data from the Developer dashboard on Stripe.

Conclusion

While stripe-ruby-mock is a great gem for quickly testing your Stripe integration, alternatives are always available. Personally, I prefer keeping my entire Gemfile up-to-date, so bidding farewell to the gem was my best choice. I hope this post gives you some ideas if you're aiming for the same goal.

YAMAP テックブログ

Discussion