paint-brush
Testing Stripe with Rails and RSpecby@rob__race
10,460 reads
10,460 reads

Testing Stripe with Rails and RSpec

by Rob RaceOctober 31st, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

While Stripe may not be available everywhere or may not be everyone’s most cost-efficient card processor, I have found time and time again that their API and Dashboard are well thought out and “just work”.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Testing Stripe with Rails and RSpec
Rob Race HackerNoon profile picture

While Stripe may not be available everywhere or may not be everyone’s most cost-efficient card processor, I have found time and time again that their API and Dashboard are well thought out and “just work”.

Testing Stripe will be a bit more involved than most another testing you will do within your application. Not only are you responsible for testing against the behavior of your application, but also keeping track of remote objects and responses on a third party API(Stripe). I am going to spare some of the details on getting Stripe and running in your application as it seems that it is covered many places and focus on some high-level techniques to test Stripe in your Rails application, using RSpec.

If this is the sort of content you like, then you will be able to see a full implementation(including a comprehensive test suite) from my upcoming book Build A SaaS App in Rails 6. The book guides you from humble beginnings through deploying an app to production. The book is now in pre sale, and you can grab a free chapter right now!

Also, the beta for my new project Pull Manager is has been released. If you’re losing track of pull requests, have old ones lingering around or would just love a dashboard that aggregates these requests over multiple services (Github, Gitlab, and Bitbucket), check it out.

stripe-ruby-mock

stripe-ruby-mock is a gem that allows you to test against Stripe without actually hitting the external Stripe API servers(unless you want to, and can explicitly do so). This will enable you to not worry about external connections, cleaning up test data and complete control over any mocked data.

Installation is pretty standard for a Rails app:

Add gem 'stripe-ruby-mock', '~> 2.5.0', :require => 'stripe_mock' and then follow that up with a bundle install . Then, to get it started, you can add the following to your spec_helper.rb :




config.before(:each) do@stripe_test_helper = StripeMock.create_test_helperStripeMock.startend



config.after(:each) doStripeMock.stopend

Which will start the mock server and make a handful of helper methods(such as creating a plan or user in Stripe)

Once installed, you can follow along with this hypothetical Controller spec:


RSpec.describe PlansController, type: :controller dologin_admin







describe 'GET #index' doit 'returns http success' doget :indexexpect(response).to have_http_status(:success)expect(assigns(:plans).count).to eq(3)endend










describe 'GET #show' dolet(:plan) { @stripe_test_helper.create_plan(id: 'free', amount: 0) }before(:each) doPaymentServices::Stripe::Subscription::CreationService.(user: @admin,account: @admin.account,plan: plan.id)@admin.reloadend











it 'returns http success starter token' do@stripe_test_helper.create_plan(id: 'starter', amount: 10)cus =Stripe::Customer.retrieve(@admin.account.subscription.stripe_customer_id)card = cus.sources.create(source: @stripe_test_helper.generate_card_token)@admin.account.subscription.update(stripe_token: card.id)get :show, params: {id: 'starter'}expect(response).to have_http_status(:redirect)expect(flash[:notice]).to eq 'Plan was updated successfully.'end

In this controller spec(which is partial for the brevity of the example), there is a spec to make sure the index action displays the right number of plans based on the local or remote plan retrieval methods you use.

Personally, I like to use a local yml file that will sync plans on demand.

Next, the good stuff. The let definitions allow us to set up a plan in the mocked Stripe server, as well as creating a subscription(here based on a Service Object that handles Stripe Customer and Stripe Subscription creation). Inside the specific show spec, another plan is created(that requires a source/card) and a new source/card is added through the Stripe Ruby gem’s interface. In this case, Stripe want’s you to grab the Customer object, then make an update to sources through that customer. Once done, it is saved to the local subscription record. Lastly, the show action will do its thing now with a Stripe Customer, Subscription and Card already connected to the local user in the spec.

This is just a small sample of how to write controller actions that interface with Stripe. To move on, let’s see a feature spec that is hitting the sign up workflow, which now sports some Plans and Stripe API interactions.



require 'rails_helper'include ActiveJob::TestHelperActiveJob::Base.queue_adapter = :test


RSpec.feature "SignUpProcesses", type: :feature doit "should require the user to sign up and successfully sign up" do

visit root_path

click_on 'Sign up'

find(:xpath, "//a[@href='/account/plans/free']").click






within "#new_user" dofill_in "user_name", with: 'Test'fill_in "user_email", with: '[email protected]'fill_in "user_password", with: 'password123'fill_in "user_password_confirmation", with: 'password123'end

click_button "Sign Up"

expect(current_path).to eql(new_accounts_path)



within "#new_account" dofill_in "account_name", with: "Test Co"end






expect doclick_button "Save"expect(ActionMailer::Base.deliveries.last.to).to eq ["[email protected]"]expect(current_path).to eql(root_path)endend

While this doesn’t make any direct use of StripeMock’s helpers or server, it will actually save all Customer and Subscription changes to the local StripeMock instance(which, by the way get reset between every test).

Anyway, clicking Sign Up will take you to a plans#index page. On that page, you will be able to click on a link that will select the plan to begin a trial on. Once done, that plan will be passed through the continuation of the signup process, finally, being used within a Service Object, which delegates out the Stripe creations to the same CreationService class from before.

If there were any issues with the Stripe Subscriptions, the workflow would not end up on the root_path and fail tests. You can even take this a step further bu having the feature visit the Billing section of your application and make sure the plan that was selected is the plan displayed as current.

Webhooks!

What Stripe discussion isn’t complete without mentioning Webhooks. On the off chance you have never heard of Webhooks, it means that when a third party service makes a change that is not a direct result of the API call that it came from, it will send an HTTP request to the application to handle such an event. To build a more concrete example suppose there is a payment coming up in a few days. Stripe will create an event invoice.upcoming and broadcast a HTTP request to an endpoint the Rails application specified for receiving such events. From there, it is up to your application to handle the event.

Before jumping into the spec(s) around Stripe Webhooks. Let me go ahead and give a shout out to the StripeEvent gem. As a good software maintainer, I like to make sure a gem install is well worth it. Stripe_event is a tiny gem that sets up the WebhooksController, and one file set of tiny classes to add a little bit of metaprogramming. It uses ActiveSupport::Notifications under the hood to pass the events to you handling code(which you can choose between blocks or send a .call to a class of your choice).

With that being said, testing a webhook is another quick piece of setup through StripeMock.


require 'rails_helper'include ActiveJob::TestHelper


RSpec.describe Payments::InvoicePaymentSucceeded, type: :mailer dolet(:plan) { @stripe_test_helper.create_plan(id: 'free', amount: 0) }













before(:each) do@admin = FactoryGirl.create(:user, email: '[email protected]')PaymentServices::Stripe::Subscription::CreationService.(user: @admin,account: @admin.account,plan: plan.id)@event = StripeMock.mock_webhook_event('invoice.payment_succeeded',customer: @admin.account.subscription.stripe_customer_id,subscription: @admin.account.subscription.stripe_subscription_id)end






it 'job is created' doActiveJob::Base.queue_adapter = :testexpect doPayments::InvoicePaymentSucceeded.email(@event.id).deliver_laterend.to have_enqueued_job.on_queue('mailers')end







it 'email is sent' doexpect doperform_enqueued_jobs doPayments::InvoicePaymentSucceeded.email(@event.id).deliver_laterendend.to change { ActionMailer::Base.deliveries.size }.by(1)end

The setup looks pretty similar. A plan is added, through a StripeMock helper. Then, before each spec, an @admin user is created by FactoryGirl/FactoryBot. Once created, a Customer and Subscription are setup. Finally, a mock webhook event is created. This is the important piece here, as StripeEvent or your own Webhook handling code will be retrieving the Event from Stripe. Thus, our tests will need a mocked version.

By default, StripeMock actually keeps a copy of most webhook events in JSON fixtures within the gem’s structure. However, for each type of event, you can override specific attributes. Here, to make sure the event pulls up the right customer an subscription, they are overridden in the second argument of creating the mock event.

Once that is out of the way, it is pretty much business as normal. In the case of the beginning of the spec here, upon passing in an event to the mailer, it is making sure the mailer “did its thing” and queued up mail to the right queue.

Another cool piece of StripeMock is the ability to have the specific request throw an error and thus allowing you to test your error handling logic. The syntax is pretty simple, but will require you to look up the symbol definition of a particular request type/endpoint:




StripeMock.prepare_error(Stripe::InvalidRequestError.new('s', {}),:get_charges)

Here, we’re specifying the Stripe::InvalidRequestError to be raised the next time Stripe::Charge.list is invoked.

What do you use?

That is a high level over a few different places to test Stripe. As well as some tools that could help your Stripe implementation(and tests).

What do you use or implement when utilizing Stripe(and testing it as you should)?