Testing Facebook integrations

Published: 2015-08-21 by Lars  experiencetesting

At Staance Engineering, I have implemented some of our integrations with Facebook. We want to have automated tests that ensure that all our integration scenarios work as we want them to. This blog post describes how we do that using tools like Nock and Sinon.JS. The same techniques can be applied to most other types of web integrations, say to Amazon or Google APIs.

Staance: "You should be heard"

Staance is a platform for making your voice heard and having your opinions count. Staance is designed to empower all voices while revealing valuable insights into public opinion - letting you keep your finger on the pulse of the world while helping shape it.

We integrate tightly with Facebook to make it as easy as possible for our users to access Staance and share opinions.

Integration testing - or not

We execute all our automated tests on every commit, and even more often locally on our development laptops. So it is quite important that our tests are fast and robust. We want every test to run in a few milliseconds and to be unaffected of network conditions or the current status of Facebook's production servers. So instead of testing against the real Facebook servers, we write our tests to run against fake Facebook services that we can quickly create on the fly directly in the test code.

Fake an HTTP-based API using Nock

Our back-end is written in Node.JS, and we use Nock to create fake HTTP services. When our back-end authenticates a user with Facebook credentials, we will make an HTTP request to the Facebook /me API passing in the user's Facebook access token. In our code we generate the HTTP request like this:

restler.get('https://graph.facebook.com/v2.3/me', {
  query: {
    scope: 'email',
    access_token: token
  }
}).on('complete', function(results) {
  // further processing
});

In our test we use Nock to define the fake Facebook service like this:

var nock = require('nock');
beforeEach(function () {
  var fakeFbApi = nock('https://graph.facebook.com');
  fakeFbApi // .log(console.log)
  .get('/v2.3/me?scope=email&access_token=Fake-Facebook-Token')
  .reply(200, {
    id: '1234567890',
    email: '[email protected]',
    first_name: 'Lars',
    last_name: 'Thorup',
    gender: 'male'
  });
});

This fake response has to look exactly like what Facebook would actually return for this scenario. So you cannot just come up with any suitable response when writing the test. I normally run my code manually against the real service and record the real response and then craft my fake response for the test from that. When testing with a fake response, we trust that the real service does not change over time. With Facebook, and most other external services, this trust is reasonable, because their API is versioned. When they change their API they also change the version number, so the version that we are using stays stable. If you are dealing with a non-versioned API that changes often, consider the approach I describe in Unit test your service integration layer.

Now when the test exercises the restler()-call, no HTTP request is actually made, instead the code continues with the fake response. Also note, that setting up this fake service frees us from using real Facebook identities and credentials in our tests.

Faking a JavaScript API with Sinon.JS

Our front-end for desktop and mobile web is written in JavaScript, and utilizes the official Facebook JavaScript API, that we load on our page with

<script src="//connect.facebook.net/en_US/sdk.js"></script>

This script will install a global FB property on the window object with methods for interacting with Facebook. When a user has authenticated with Facebook they can enable or disable certain permissions, such as sharing with friends. We want to adapt the user experience depending on which permissions the user has given us. In our code we ask about the current user's permissions like this:

FB.api('/me/permissions', function (response) {
  if(!response.error) {
    resolve(response.data);
  } else {
    reject(new Error(response.error));
  }
});

In our test we use Sinon.JS to fake calls to the Facebook JavaScript API and return the values we need, like this:

beforeEach(function () {
  window.FB = {
    api: sinon.stub(),
  };
  var read = {permission: 'read', status: 'granted'};
  window.FB.api.withArgs('/me/permissions').onCall(0).yields({data: [read]});
});

afterEach(function () {
    delete window.FB;
});

Now when the test exercises the FB.api() call, no Facebook code is actually being run, instead the code continues with the response from the fake API.

Fast and robust

This approach allows us to write a large number of tests to cover all the different combinations of scenarios with authenticated versus non-authenticated users, different permissions, different user actions, and run all those tests in less than a second.

Try out our new and beautifully redesigned Staance iOS App.

Discuss on Twitter