---
title: Testing SMS Interactions
teaser: How do you simulate SMS messages in feature specs?
tags: web,testing,ruby,rails
author: Joël Quenneville
published_on: 2015-07-27
---

When building an application that sends SMS, we like to use an external service
such as [Twilio][twilio] to handle the actual sending of the messages. When unit
testing parts of code that interact with SMS, you can simply stub out the actual
sending of SMS to keep your test isolated.  But what about feature specs?

[twilio]: http://twilio.com

## Writing feature specs

Take the following two user stories:

> When I make a purchase, I want to receive a link to an invoice via SMS as
> confirmation.

and

> When I sign in, I want be asked for both my password and for a four-digit code
> sent to me via SMS to ensure greater security for my account.

They both require us to interact with an SMS message. Ideally, a feature spec
would look something like:

```ruby
feature "signing in" do
  scenario "with two factors" do
    user = create(:user, password: "password", email: "user@example.com")

    visit root_path
    click_on "Sign In"

    fill_in :email, with: "user@example.com"
    fill_in :password, with: "password"
    click_on "Submit"

    secret_code = SMS::Client.messages.last # this would be so nice
    fill_in :code, with: secret_code
    click_on "Submit"

    expect(page).to have_content("Sign out")
  end
end
```

If we had some mechanism for accessing sent messages from the tests that would
make things much easier. SMS client libraries don't provide this functionality
because it would be a pointless waste of memory to store all messages sent in
production.

## Building a fake SMS client

A good solution here is to create our own test SMS client that mimics the API of
the real client but instead of sending an SMS message stores the message in
memory.

For example, looking at the documentation for the official [Twilio Ruby
gem](https://github.com/twilio/twilio-ruby) we can see that its API is the
following:

```ruby
# set up a client to talk to the Twilio REST API
@client = Twilio::REST::Client.new(account_sid, auth_token)

# send an SMS
@client.messages.create(
  from: '+14159341234',
  to: '+16105557069',
  body: 'Hey there!'
)
```

Let's build a fake client that mimics this API:

```ruby
class FakeSMS
  Message = Struct.new(:from, :to, :body)

  cattr_accessor :messages
  self.messages = []

  def initialize(_account_sid, _auth_token)
  end

  def messages
    self
  end

  def create(from:, to:, body:)
    self.class.messages << Message.new(from: from, to: to, body: body)
  end
end
```

There are a few things going on here:

* We don't need any logic for initialization
* `@client.messages` returns a new object in the real client. No need to add the
  complexity of a second object in the fake so we just return `self`.
* `create` instantiates a new `Message` object and appends it to the
  `self.class.messages` array instead of actually sending SMS.

There are a few options when it comes time to use the fake SMS client instead of
the real one.

## Stubbing the constant

RSpec offers a [`stub_const`
method](http://rspec.info/blog/2012/06/constant-stubbing-in-rspec-2-11/) that
allows us to stub constants in specs.

In our spec helper we could do:

```ruby
# spec/spec_helper.rb

RSpec.configure. do |config|
  config.before(:each) do
    stub_const("Twilio::REST::Client", FakeSMS)
  end
end
```

This aliases `Twilio::REST::Client` to refer to `FakeSMS` instead.

## Rails loading

If stubbing constants makes you uncomfortable, you can take advantage of Rails'
class loading system. Rails will only try to load a constant if it isn't already
pointing to something. By defining `Twilio::REST::Client` *before* the Twilio
gem is loaded, you avoid any constant redefinition errors.

```ruby
# config/initializers/fake_twilio.rb

if Rails.env.test?
  Twilio::REST::Client = FakeSMS
end
```

## Configuration

If you wrapped the Twilio code in an adapter to isolate the code you don't own
you have another option. Given the following adapter:

```ruby
class SMSClient
  def initialize
    @client = Twilio::REST::Client.new(
      ENV.fetch("TWILIO_ACCOUNT_SID"),
      ENV.fetch("TWILIO_AUTH_TOKEN"),
    )
  end

  def send_message(from:, to:, body:)
    @client.messages.create(from: from, to: to, body: body)
  end
end
```

The adapter can easily be modified to allow it to accept different clients:

```ruby
class SMSClient
  cattr_accessor :client
  self.client = Twilio::REST::Client

  def initialize
    @client = self.class.client.new(
      ENV.fetch("TWILIO_ACCOUNT_SID"),
      ENV.fetch("TWILIO_AUTH_TOKEN"),
    )
  end

  # more methods
end
```

Then in the spec_helper we can change the config:

```ruby
# spec_helper.rb

SMSClient.client = FakeSMS
```

## Reseting the messages queue

You may want to reset `FakeSMS.messages` between each test. To do so, add the
following to your `spec_helper`:

```ruby
RSpec.configure do |config|
  config.before :each, type: :feature do
    FakeSMS.messages = []
  end
end
```

## Conclusion

Once we pick one of the options above, our feature spec should run successfully
after making a few tweaks.

```ruby
feature "signing in" do
  scenario "with two factors" do
    user = create(:user, password: "password", email: "user@example.com")

    visit root_path
    click_on "Sign In"

    fill_in :email, with: "user@example.com"
    fill_in :password, with: "password"
    click_on "Submit"

    last_message = FakeSMS.messages.last # this now returns a message object
    fill_in :code, with: last_message.body # the code is the body of the message
    click_on "Submit"

    expect(page).to have_content("Sign out")
  end
end
```

Using a fake SMS client is a clean way to allow feature specs to test flows that
require interaction with SMS messages. Although we looked at mimicking Twilio's
client here, this approach can be used for any SMS provider.
