---
title: 'JavaScript Integration Testing Example: Installing And Using Mixpanel'
teaser:
tags: web,javascript,rails,testing
author: Dan Croak
published_on: 2011-03-22
---

We've been on [a
quest](https://thoughtbot.com/blog/post/1658763359/thoughtbot-and-the-holy-grail)
for years to make sure our integration tests cover the JavaScript components of
the app. Here's an example: installing [Mixpanel](http://mixpanel.com) in
[Copycopter](http://copycopter.com) to track visits, user sign ups, user
activity during the free trial, and subscriptions.

`Gemfile`:

```ruby
gem 'capybara-webkit'
```

`features/support/env.rb`:

```ruby
Capybara.javascript_driver = :webkit
```

`features/mixpanel.feature`:

```cucumber
Background:
  Given the following plan exists:
    | id | name       | price | trial |
    | 16 | Supersonic | 5     | false |
    | 5  | Trial      | 0     | true  |
  And the following limits exist:
    | plan             | name     | value |
    | name: Supersonic | users    | 13    |
    | name: Supersonic | projects | 14    |

@javascript
Scenario: Track visitor learning about Copycopter
  When I go to the homepage
  Then mixpanel should track the "visited-home" event
  When I follow "Take a tour"
  Then mixpanel should track the "clicked-tour" event
  When I follow "Next, view plans and pricing"
  Then mixpanel should track the "clicked-next-to-plans-and-pricing" event

@javascript
Scenario: Track visitor signing up for free trial
  When I go to the homepage
  And I follow "Plans and Pricing"
  Then mixpanel should track the "clicked-plans-and-pricing" event
  When I follow "Choose free trial"
  Then mixpanel should track the "viewed-plan" event with the properties:
    | plan_id | 5 |
```

We use the `@javascript` tag, which will use [Capybara
Webkit](https://github.com/thoughtbot/capybara-webkit) to drive the browser in
these tests.

We explicitly set the id's of the ActiveRecord objects so we can check that
Mixpanel receives the right plan id's using their [properties
feature](https://mixpanel.com/help/reference#properties).

`features/step_definitions/mixpanel_steps.rb`:

```ruby
Then %r{^mixpanel should track the "(.*)" event$} do |event_name|
  mpq = JSON.parse(evaluate_script(%{JSON.stringify(mpq);}))
  mpq.should include(["track", event_name])
end

Then %r{^mixpanel should track the "(.*)" event with the properties:$} do |event_name, table|
  mpq        = JSON.parse(evaluate_script(%{JSON.stringify(mpq);}))
  properties = table.transpose.hashes.first
  mpq.should include(["track", event_name, properties])
end
```

This is a little funky. We're using `JSON.stringify` via
[json2.js](https://github.com/douglascrockford/JSON-js/raw/master/json2.js) and
then Ruby's JSON.parse to convert Mixpanel's `mpq` Javascript object into its
Ruby equivalent in order to invoke expectations on it.

Therefore, we need to include `json2.js` in our app:

    curl https://github.com/douglascrockford/JSON-js/raw/master/json2.js > public/javascripts/json2.js

`app/views/shared/_javascript.html.erb`:

```erb
<% if Rails.env.test? %>
  <%= javascript_include_tag "json2" %>
<% end %>
```

That smells like a hack, but whatever...

Also in that partial, the actual setup for Mixpanel:

```erb
<script type="text/javascript">
  var mpq = [];
  <% if Rails.env.staging? || Rails.env.production? -%>
    mpq.push(["init", "<%= MIXPANEL_TOKEN %>"]);
    (function() {
      var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
      mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
      var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
    })();
  <% end -%>
</script>
```

That `mpq` object looks familiar. We're testing against it in our integration
suite. It's just a JavaScript Array.

We only include the rest of the Mixpanel setup in staging and production. It
stuffs `mixpanel.js` into the DOM asynchronously.

We interpolate our Mixpanel account's token based on the environment so we can
run acceptance on our user story on staging.

`config/environments/staging.rb`:

    MIXPANEL_TOKEN = "our-staging-token".freeze

`config/environments/production.rb`:

    MIXPANEL_TOKEN = "our-production-token".freeze

To get the rest of the integration test passing, we follow the Mixpanel <abbr
title="Application Programming Interface">API</abbr> normally.

`views/homes/show.html.erb`:

```erb
$(function () {
  mpq.push(["track", "visited-home"]);
  $("#tour-cloud").click(function () {
    mpq.push(["track", "clicked-tour"]);
  });
  $("#plans-cloud").click(function () {
    mpq.push(["track", "clicked-plans-and-pricing"]);
  });
  $("#next-to-plans").click(function () {
    mpq.push(["track", "clicked-next-to-plans-and-pricing"]);
  });
});
```

`views/accounts/new.html.erb`:

```erb
    $(function () {
      mpq.push(["track", "viewed-plan", { plan_id: "<%= @plan.id %>" }]);
    });
```

This use case is relatively common. Include some external service's JavaScript
and use their Javascript <abbr title="Application Programming
Interface">API</abbr> in order to get good analytics on the app.

To make it happen smoothly, there's a lot of interpolation and Ruby mixing with
HTML and JavaScript. Things could go wrong and it feels good to have
integration coverage for it.
