Intermediate TDD in Rails : Subscription using Stripe API

Objective


To implement subscription feature.

Steps


Step 1

We need to create a plan once for testing purposes. Create a rake task, stripe_plan.rake in lib/tasks/stripe_plan.rake:

desc 'Create Plans for testing'
namespace :stripe do
  task :create_plan => :environment do
    Stripe.api_key = 'sk_test_watHqWl2XD88WHqhknk7sqXN'
    Stripe.api_version = "2015-02-18"

    plan = Stripe::Plan.create(
      amount: 2000,
      interval: 'month',
      name: 'Test Plan',
      currency: 'usd',
      id: 'test')

    puts plan.inspect
  end
end

Step 2

Run the rake task:

$rake stripe:create_plan. 

The output shows the attributes of the new test plan.

<Stripe::Plan:0x3ffd2588d990 id=test> JSON: {
  "id": "test",
  "interval": "month",
  "name": "Test Plan",
  "created": 1426635709,
  "amount": 2000,
  "currency": "usd",
  "object": "plan",
  "livemode": false,
  "interval_count": 1,
  "trial_period_days": null,
  "metadata": {},
  "statement_descriptor": null
}

Step 3

Write a test to subscribe a customer to a given plan.

  it 'should subscribe a customer to a given plan' do
    VCR.use_cassette "create subscription" do
      stripe_plan_id = 'test'
      token = Stripe::Token.create(
        card: {
          number: "4242424242424242",
          exp_month: 3,
          exp_year: 2016,
          cvc: "314"
        }
      )
      customer = StripeGateway.create_customer(token.id, 'description')
      stripe_customer_id = customer.id

      subscription = StripeGateway.create_subscription(stripe_customer_id, stripe_plan_id)

      expect(subscription.id.size).to be > 2
    end
  end

Run the test.

$rspec spec/gateway/stripe_gateway_spec.rb 

Step 4

Implement the subscription feature in app/gateway/stripe_gateway.rb.

  def self.create_subscription(stripe_customer_id, stripe_plan_id)
    customer = Stripe::Customer.retrieve(stripe_customer_id)
    customer.subscriptions.create(plan: stripe_plan_id)
  end

Run the test.

Step 5

It fails.

  1) StripeGateway should subscribe a customer to a given plan
     Failure/Error: subscription = StripeGateway.create_subscription(stripe_customer_id, stripe_plan_id)
     VCR::Errors::UnhandledHTTPRequestError:


       ================================================================================
       An HTTP request has been made that VCR does not know how to handle:
         GET https://api.stripe.com/v1/customers/cus_5tHIlHbNEjqssk

       VCR is currently using the following cassette:
         - /Users/zepho/projects/tdd/basics/blog/spec/fixtures/vcr_cassettes/create_subscription.yml
         - :record => :once
         - :match_requests_on => [:method, :uri]

       Under the current configuration VCR can not find a suitable HTTP interaction
       to replay and is prevented from recording new requests. There are a few ways
       you can deal with this:

         * If you're surprised VCR is raising this error
           and want insight about how VCR attempted to handle the request,
           you can use the debug_logger configuration option to log more details [1].
         * You can use the :new_episodes record mode to allow VCR to
           record this new request to the existing cassette [2].
         * If you want VCR to ignore this request (and others like it), you can
           set an `ignore_request` callback [3].
         * The current record mode (:once) does not allow new requests to be recorded
           to a previously recorded cassette. You can delete the cassette file and re-run
           your tests to allow the cassette to be recorded with this request [4].

       [1] https://www.relishapp.com/vcr/vcr/v/2-9-3/docs/configuration/debug-logging
       [2] https://www.relishapp.com/vcr/vcr/v/2-9-3/docs/record-modes/new-episodes
       [3] https://www.relishapp.com/vcr/vcr/v/2-9-3/docs/configuration/ignore-request
       [4] https://www.relishapp.com/vcr/vcr/v/2-9-3/docs/record-modes/once

Since I changed the tests a few times and ran the changed test many time. It results in the above error.

Step 6

Let's delete create_subscriptions.yml and run the test again. All the tests will pass. You can login to the Stripe account and see the calls made to the Stripe server by looking at the Logs under the Requests. If you go to the Customers and click on the customer link you can see the details of the subscription.

Step 7

When the tests are run, you can see significant improvement in the test run time.

$rspec spec/gateway/stripe_gateway_spec.rb 
..

Finished in 1.75 seconds (files took 2.63 seconds to load)
2 examples, 0 failures

Coverage report generated for RSpec to /Users/zepho/projects/tdd/basics/blog/coverage. 7 / 7 LOC (100.0%) covered.
$rspec spec/gateway/stripe_gateway_spec.rb 
..

Finished in 0.32936 seconds (files took 2.59 seconds to load)
2 examples, 0 failures

Coverage report generated for RSpec to /Users/zepho/projects/tdd/basics/blog/coverage. 7 / 7 LOC (100.0%) covered.

Step 8

I want to bundle all the methods related to subscription into it's own class. This will make it easy during maintenance to see which methods belong to which use case. So the code now looks like this:

module StripeGateway

  class Subscription
    def self.create_customer(token, description)
      Stripe::Customer.create(description: description,  source: token)
    end

    def self.create(stripe_customer_id, stripe_plan_id)
      customer = Stripe::Customer.retrieve(stripe_customer_id)
      customer.subscriptions.create(plan: stripe_plan_id)
    end    
  end

end

The corresponding tests are:

require 'rails_helper'
require 'spec_helper'

describe StripeGateway::Subscription do
  before do
    Stripe.api_key = 'sk_test_watHqWl2XD88WHqhknk7sqXN'
    Stripe.api_version = "2015-02-18"
  end

  it 'should create a customer' do
    VCR.use_cassette "create customer" do
      token = Stripe::Token.create(
        card: {
          number: "4242424242424242",
          exp_month: 3,
          exp_year: 2016,
          cvc: "314"
        }
      )

      customer = StripeGateway::Subscription.create_customer(token.id, 'description')

      expect(customer.id.size).to be > 2 
    end
  end

  it 'should subscribe a customer to a given plan' do
    VCR.use_cassette "create subscription" do
      stripe_plan_id = 'test'
      token = Stripe::Token.create(
        card: {
          number: "4242424242424242",
          exp_month: 3,
          exp_year: 2016,
          cvc: "314"
        }
      )
      customer = StripeGateway::Subscription.create_customer(token.id, 'description')
      stripe_customer_id = customer.id

      subscription = StripeGateway::Subscription.create(stripe_customer_id, stripe_plan_id)

      expect(subscription.id.size).to be > 2
    end
  end
end

Step 9

Run

$rake spec

There are two failures due to VCR :

rspec ./spec/features/home_page_spec.rb:5 # Home Page must load
rspec ./spec/features/home_page_spec.rb:11 # Home Page has link to my blog

Step 10

Add :

c.allow_http_connections_when_no_cassette = true

to the VCR.config block in rails_helper.rb:

VCR.configure do |c|
  c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
  c.hook_into :webmock # or :fakeweb
  c.allow_http_connections_when_no_cassette = true
end

Running rake spec will now run all tests successfully. You can open coverage/index.html to view the coverage report.

Summary


In this lesson we did the following:

  1. Created a plan using rake task.
  2. Wrote a test to subscribe a customer to a plan.
  3. Implemented the subscription feature.
  4. Learned how to record network interactions when a test using VCR fails.
  5. Configured VCR to turn off VCR for local network calls for feature tests.


Related Articles


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.