Developer Founder

« See the homepage for more articles.

When your app does things with other web APIs, you want some way of testing them. In this article I will show you how you can use a few simple Cucumber steps and Tinder for testing.

Letting it all run live

I am assuming here, that you want to integration test Cucumber by letting it run with a live Campfire user and room. This way you can make sure your code will always work in a production environment.

This is not ideal when you are working on a public project and you can't save credentials in your tests or when you want to run your tests without internet connection.

I think there are three other great ways of testing external APIs, but those are subject for some other post.

Cucumber it up

Here is my Cucumber feature:

  @wip
  Scenario: Push a new commit to a GitHub repository
    Given a project with a GitHub repository and a campfire room
    When I push my changes to origin
    Then the cucumber and unit test should be run
    And I should receive a link to the report in the campfire room

The approach that I am taking here is to assert if the message I am expecting is existent in the Room transcript for today.

This is the step definition for the final step, where I assert if the message has been posted to Campfire.

Then /^I should receive a link to the report in the campfire room$/ do
  campfire = Tinder::Campfire.new @project.campfire_subdomain, :token => @project.campfire_token
  @room = campfire.find_room_by_name @project.campfire_room_name
  messages = @room.transcript(Date.today)

  assert_not_nil messages.index { |m| m[:message] =~ /Build ran for #{@project.name}/}, "Expected to see build ran message in the Campfire room"
end

What this step does is create a new Tinder::Campfire object that connects with our Campfire bot account. Then, it fetches the messages in the room by getting today's transcript. Finally, it asserts if there is a message with the content Build ran for #{@project.name} to see if the message has ben posted.

Fixing one final problem

As you might know, Campfire transcripts are batched by day. This means that our test is broken since the test will always pass if we've run the test successfully once since the message we are looking up will always exist in the transcript for that day.

We can improve our test by checking if the message we are checking for in the assertion, to be posted later than a certain timestamp when this test was started. Here is the code of the new assertion line:

assert_not_nil messages.index { |m| m[:message] =~ /Build ran for #{@project.name}/ && m[:timestamp] >= @start_of_test}, "Expected to see build ran message in the Campfire room"

We've added a check if the timestamp of the message is greater than or equal to @startoftest. Now we need to initialize this variable somewhere.

I find this less than idea, but I added an extra step Given I am in the Campfire room in the feature to set this @startoftest timestamp variable, making the feature look like:

  Scenario: Push a new commit to a GitHub repository
    Given a project with a GitHub repository and a campfire room
    Given I am in the Campfire room
    When I push my changes to origin
    Then the cucumber and unit test should be run
    And I should receive a link to the report in the campfire room

And the step:

Given /^I am in the Campfire room$/ do
  @start_of_test = Time.now
end

Other, and maybe better solutions

Doing an integration test with all the components doing their actual real world thing is the best test you can imagine if you want to be sure everything works. However, it is not ideal for many reasons.

Here are a few other suggestions:

  • Using web request mocking so you use Tinder as you would normally do but simulate responses to it as if you were working on the Campfire service.
  • Using mock objects for all Tinder-related objects and methods and asserting if they are called correctly.
  • Monkey-patching Tinder to call "fake" requests in some kind of "test" mode just like ActionMailer::Base. Then, put all the method calls of room.speak into an array so you can assert on them just like you would with ActionMailer::Base.deliveries