Facebooker and Cucumber: Within the Canvas and Without

As you may already be aware, Facebooker is an awesome Ruby on Rails plugin which allows you to easily build up Rails applications which interface with Facebook.

I've worked with Facebooker for a few projects now, but I've always managed to avoid needing to test it with Cucumber. Until now.

The whole process was a little non-obvious, especially when you want to write features for pages within a Facebook Canvas as well as for those outside of it (eg. a REST API on your own site, or other, non-Facebook parts of your site).

This post is going to go through setting this up, how to get Cucumber working with Facebooker nicely (to some degree anyway), and some step definitions which I have been using to help make life easier.

One (possibly major) caveat though, I haven't got any Scenarios in place which require redirects, or testing the output of the redirect. For some reason, I keep getting the "You are being redirected" page as Webrat isn't following the 302 correctly. Something to look into.

Anyhu, a bit of an overview of how Facebooker and Cucumber work together.

Overview

When your production app uses Facebooker, a lot of the grunt work to do with authentication, making requests, etc. is handled by Facebooker::Session and Facebooker::Service. Facebooker::Session deals with authenticating you to Facebook, and is used to dispatch a lot of the requests to the Facebook API.

Behind the scenes, Facebooker::Service takes those low level requests and uses different adaptors to make the request to FB over the wire.

When working within Cucumber, obviously we won't have a real session with Facebook and we won't want real requests to be made. Instead, the Facebooker::Mock::Session and Facebooker::Mock::Service classes are used, and these help to stub out the low level calls.

In the case of Facebooker::Mock::Session, this means that you can specify the UID of the Facebook user loading up your page. In the case of Facebooker::Mock::Service, we are able to place XML fixtures under features/support/facebook which will be parsed instead of accessing the network.

Kicking Cucumber until it works

So, how do we kick these into gear in Cucumber?

Most of the heavy lifting is done by simply letting Facebooker set up your Cucumber world, rather than the default Rails world.

This is done simply by changing your features/support/env.rb like so :

When working out how to test with Facebooker, the biggest challenge I had was working out what was and wasn't essential to instantiate the right state for testing - there are blog posts around which have a rather complex step for setting up the environment, but I found that - in reality - all that is required is very simple.

This is the simplest session code I could put together. You can check out a more complete set of steps here (which we're using for our tests).

Nothing all that complex - we open a new session, and set the default for our requests with a few of the common parameters from Facebook. Probably the biggest issue I had here was grokking @integration_session - I'm still not confident I fully understand how deep it runs, but it looks to be some sort of Cucumber magic that sets the session used within Rails.

We can then do something like :

When it comes to actually mapping a model to your Facebook ID, we're using facebooker_authentication - it's simple and does the trick really well.

How to break what should otherwise work

The most important thing to remember is to be very careful if you need to stray from the default code that facebooker_authentication generates, as you can easily break the points where the Facebooker::Mock::Session is passed around. It's not instantly obvious when you make this mistake, but you will begin to see errors complaining about it not being a valid session - that's the Facebook API telling you that your bogus session is, well, bogus!

In our case, we did need to stay from the default - by default .for_facebook_id creates the record as soon as a FB user hits the app, which we didn't want. In our case, the user has to activate their account using an external process (an SMS in our case).

As mentioned above, our initial attempts to replace for_facebook_id were problematic, as we kept getting a real Facebooker::Session, rather than a Facebooker::Mock::Session. Once I worked this out though, the fix was simple - adding a way for us to pass in the already loaded facebook_session object.

Once that was done everything began humming along nicely.

However, in the most common case, for_facebook_id() will do the magic for you.

Canvas, not-Canvas, and everything inbetween

The final issue was getting Facebooker to work for non-canvas pages - pages which are requested outside of the Facebook layout.

In our case, we have a REST API that we wanted to test, but you could feasibly use this same approach to test your front end code with Cucumber if you have it all packaged in the one project.

The biggest issue was that, once Cucumber is set up for Facebooker, it expects all your requests to be using a Facebooker integration session created by open_session.

Thankfully, we can tell Facebooker to forget that we are within a canvas page, and everything begins to work again :

Then, any of your non-FB Scenarios just need to have "Given I am not in Facebook" as a Background step.

In fact, it would even be very easy to set up Tagged Hooks to do this for you automagically.