---
title: Testing Third Party Interactions
teaser: How to test integrations with a third-party API.
tags: web,ruby,testing
author: Joël Quenneville
published_on: 2017-03-15
---

When it comes to testing interactions with third-party APIs, it there are a
bewildering set of approaches and tools to work with. We've written [several]
articles [listing] various approaches as well as devoting a chapter to it in our
new book [Testing Rails]. So how do you know which one to use?

Taking a closer look at these, it turns out they all fall into one of two
testing approaches testing on one of two levels. These form a 2x2 matrix

    (Adapter | HTTP) x (Stubbed | Real)

![](https://images.thoughtbot.com/jq-testing-third-party-interactions/QvEMjBlDQMylYBKQDGfl_testing_type_matrix.jpg)

[Testing Rails]: http://testingrailsbook.com
[several]: https://thoughtbot.com/blog/fake-it
[listing]: https://thoughtbot.com/blog/how-to-stub-external-services-in-tests

## Stubbing the adapter

You have an object that encapsulates your third party interactions. In tests
that interact with that object, you stub it so that it doesn't really make a
network request but instead returns a canned value.

For example, you are testing a shopping cart's tax calculation functionality but
the prices for the items are dynamic and are calculated by a separate quote
service:

```ruby
require "rails_helper"

describe ShoppingCart do
  it "calculates tax" do
    item = create(:item)
    shopping_cart = ShoppingCart.new(item)

    allow(QuoteService).to receive(:fetch).and_return(100)

    expect(shopping_cart.tax).to eq 10
  end
end
```

This is particularly useful when **unit-testing** objects that depend on data
from other services.

Beware of [stubbing the system under test]! Don't stub `QuoteService` when
testing `QuoteService`.

In general, you also want to _avoid_ stubbing in an **integration/feature spec**
since those try to test the whole stack and you probably want the `QuoteService`
to be tested as part of your end-to-end testing.

[stubbing the system under test]: https://thoughtbot.com/blog/don-t-stub-the-system-under-test

## Stubbing the network

So what if you want the `QuoteService` to execute but don't want to actually
make a network request? Stubbing the network is a popular way to do this. The
idea is the `QuoteService` _thinks_ it's making HTTP requests but in reality it
is being given canned answers.

There are many ways of accomplishing this:

* Manually stubbing the HTTP library (e.g. [`HTTParty`] or [`Net::Http`])
* Using a gem like [`Webmock`] which returns arbitrary values when making HTTP
  requests
* Using a gem like [`VCR`] that records the real response of your API and then
  replays it instead of making a real HTTP request.

For example, here we use Webmock to stub the HTTP layer. Note that this test
exercises both the `ShoppingCart` and the `QuoteService` so it's _not_ a pure
unit test.

```ruby
require "rails_helper"

describe ShoppingCart do
  it "calculates tax" do
    item = create(:item)
    shopping_cart = ShoppingCart.new(item)

    stub_request("http://quote-service.com/prices").and_return(
      { pricesRequest: { regularPrice: 100 } }.to_json
    )

    expect(shopping_cart.tax).to eq 10
  end
end
```

This approach is particularly helpful in **integration/feature specs** since you
can exercise the whole system without worrying about making HTTP requests.

Writing a bunch of ad-hoc stubs can get messy if you have a lot of
them. This approach works best in one-off request situations.

[`HTTParty`]: https://github.com/jnunemaker/httparty
[`Net::Http`]: https://ruby-doc.org/stdlib-2.4.0/libdoc/net/http/rdoc/Net/HTTP.html
[`Webmock`]: https://github.com/bblimke/webmock
[`VCR`]: https://github.com/vcr/vcr

## Swapping out the adapter

Instead of stubbing the your domain object that handles interactions with the
third party API, this approach drops in a test version that doesn't make HTTP
requests instead.

For example, if you're testing 2-factor auth and don't want to actually send out
SMS in your test but also want to assert on the texts sent, you might write a
test like:

```ruby
feature "signing in" do
  scenario "with two factors" do
    create(:user, password: "password", email: "user@example.com")

    visit root_path
    click_on "Sign In"

    fill_in :email, with: "user@example.com"
    fill_in :password, with: "password"
    click_on "Submit"

    last_message = FakeSMS.messages.last
    fill_in :code, with: last_message.body
    click_on "Submit"

    expect(page).to have_content("Sign out")
  end
end
```

`FakeSMS` is a drop-in replacement for your regular `TwilioSMS` class that would
actually send messages over the wire but `FakeSMS` stores the messages in memory
instead. This is a special take on the strategy pattern

This approach is useful in **integration/feature specs**, particularly when you
need to assert on data that has been sent or received from the service.

Note that because we're using the `FakeSMS` adapter instead of the
`TwilioAdapter`, the code in `TwilioAdapter` doesn't get exercised in our test.

We go more in depth on using this approach this article on [testing SMS].

[testing SMS]: https://thoughtbot.com/blog/testing-sms-interactions

## Swapping out the server

All the previous approaches have had one flaw: you can't _fully_ test your
application from end to end. There's always _some small part_ that gets stubbed
or swapped to prevent actual HTTP requests from taking place. Generally that's
OK as long as you are aware of the tradeoffs.

This final approach asks "what if we didn't try to prevent HTTP?". Instead of
trying to prevent HTTP, you make _real HTTP requests_ to a _local server_.

You write up a small fake service (perhaps using [Sinatra]) and then configure
your application to point to your local service in tests rather than the real
one. These fakes can be shared and reused. For example, we've created the
[`fake_stripe`] gem that provides a fake for the [Stripe] payment service.

In addition to allowing full end-to-end testing of your app, this approach
scales nicely too. If you have a process that requires a lot of requests, such
as OAuth or some other token exchange auth system, working with a fake server is
a clean way to deal with all those requests going back and forth.

We've [written about this approach] in depth before. In addition to allowing you
to test fully end-to-end, the fake local server you create can be reused as a
local or staging sandbox.

[Sinatra]: http://www.sinatrarb.com/
[Stripe]: https://stripe.com/
[`fake_stripe`]: https://github.com/thoughtbot/fake_stripe

## The Weekly Iteration

This article is inspired by an interview on the with [Chris Toomey] on [The
Weekly Iteration]

<a href="https://thoughtbot.com/upcase/videos/testing-interaction-with-3rd-party-apis">
  <img
    src="https://images.thoughtbot.com/jq-testing-third-party-interactions/eHahQiWvT8asUR3vnkfM_joel_and_chris.jpg"
  />
</a>

[Chris Toomey]: https://twitter.com/christoomey
[The Weekly Iteration]: https://thoughtbot.com/upcase/videos/testing-interaction-with-3rd-party-apis
[written about this approach]: https://thoughtbot.com/blog/faking-apis-in-development-and-staging
