How to stub external services in tests
Requests to external services during test runs can cause several issues:
- Tests failing intermittently due to connectivity issues.
- Dramatically slower test suites.
- Hitting API rate limits on 3rd party sites (e.g. Twitter).
- Service may not exist yet (only documentation for it).
- Service doesn’t have a sandbox or staging server.
When integrating with external services we want to make sure our test suite isn’t hitting any 3rd party services. Our tests should run in isolation.
Disable remote connections
First, let’s make sure our test suite can’t make external requests by disabling them in our `spec_helper.rb`:
Verify that any external requests will break the build:
As expected we now see errors when external requests are made:
You can stub this request with the following snippet:
We can fix this by stubbing any requests to `api.github.com` with Webmock, and returning pre-defined content.
Run the test again and now it will pass.
Another approach for preventing external requests is to record a live interaction and “replay” it back during tests. The VCR gem has a concept of cassettes which will record your test suites outgoing HTTP requests and then replay them for future test runs.
Considerations when using VCR:
- Communication on how cassettes are shared with other developers.
- Needs the external service to be available for first test run.
- Difficult to simulate errors.
We’ll go a different route and create a fake version of the GitHub service.
Create a Fake (Hello Sinatra!)
When your application depends heavily on a third party service, consider building a fake service inside your application with Sinatra. This will let us run full integration tests in total isolation, and control the responses to our test suite.
First we use Webmock to route all requests to our Sinatra application, `FakeGitHub`.
Next we’ll create the `FakeGitHub` application.
Download a sample JSON response and store it in a local fixture file `spec/support/fixtures/contributors.json`.
Update the test, and verify the expected stub response is being returned.
Run the specs.
$ rspec spec/features/external_request_spec.rb
Finished in 0.04713 seconds
1 example, 0 failures
Voilà, all green! This now allows us to run a full integration test without ever having to make an external connection.
A few things to consider when creating a fake:
- A fake version of a service can lead to additional maintenance overhead.
- Your fake could get out of sync with the external endpoint.