---
title: 'Testing Paperclip on S3 with Cucumber &amp; Factory Bot '
teaser:
tags: web,rails,testing,paperclip,factory_girl,factory_bot
author: Dan Croak
published_on: 2009-08-25
---

We've been using [Heroku](http://heroku.com) as a staging environment for our
latest project. One constraint is a read-only filesystem.

The most apparent effect of this is we cannot allow users to upload files to
the filesystem.

Fine. [Paperclip has an S3 storage
option](http://rdoc.info/rdoc/thoughtbot/paperclip/blob/4f9ffb856be76fcc2cc085c919f2fd472593291a/Paperclip/Storage/S3.html).

## Testing

Webrat has a very nice existing convention for interacting with file fields:

```cucumber
When /^I attach the file at "([^\"]*)" to "([^\"]*)"$/ do |path, field|
  attach_file(field, path)
end
```

Unfortunately, if we have an S3-backed model like this...

```ruby
has_attached_file :logo,
  :path           => ":attachment/:id/:style.:extension",
  :storage        => :s3,
  :s3_credentials => {
    :access_key_id     => ENV['S3_KEY'],
    :secret_access_key => ENV['S3_SECRET']
  },
  :bucket         => ENV['S3_BUCKET']
```

... then we're going to be doing a RESTful PUT to S3 during each test run.

Incidentally, those ENV variables are Heroku's [config
vars](http://docs.heroku.com/config-vars). The idea is that you keep that
configuration separated from your source control.

## Total integration

You could argue that these PUTs to S3 are a good thing because your Cucumber
feature will represent total integration.

While that's true, I'd rather not pay bandwidth costs and I'm comfortable as
long as the correct interface to S3 was called.

So what we've landed on is something like this:

```cucumber
Given I am on the new band page
When I attach a "demo_tape" "mp3" file to a "band" on S3
And I press "Upload demo tape"
Then I should see "Band was successfully created"
```

The only non-standard Webrat step is our new S3 step. Let's take a look at it:

```cucumber
# features/step_definitions/paperclip_steps.rb

When /^I attach an? "([^\"]*)" "([^\"]*)" file to an? "([^\"]*)" on S3$/
  do |attachment, extension, model|
  stub_paperclip_s3(model, attachment, extension)
  attach_file attachment,
              "features/support/paperclip/#{model.gsub(" ", "_").underscore}/#{attachment}.#{extension}"
end
```

The `stub_paperclip_s3` method is coming from a custom Shoulda Macro:

```ruby
# test/shoulda_macros/paperclip.rb

module Paperclip
  module Shoulda
    def stub_paperclip_s3(model, attachment, extension)
      definition = model.gsub(" ", "_").classify.constantize.
                          attachment_definitions[attachment.to_sym]

      path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
      path.gsub!(/:([^\/\.]+)/) do |match|
        "([^\/\.]+)"
      end

      FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
    end

    def paperclip_fixture(model, attachment, extension)
      stub_paperclip_s3(model, attachment, extension)
      base_path = File.join(File.dirname(__FILE__), "..", "..",
                            "features", "support", "paperclip")
      File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
    end
  end
end

class ActionController::Integration::Session
  include Paperclip::Shoulda
end

class Factory
  include Paperclip::Shoulda
end
```

## Fakeweb and more conventions

We're using the [Fakeweb](http://fakeweb.rubyforge.org) gem like we normally
use mocking: expect that something happened, and stop it from actually
happening.

We're also leaning on conventions similar to the [actor
directory](https://thoughtbot.com/blog/post/164479801/cucumber-directory-convention)
convention we're also trying.

In this case, we're expecting our features directory to look like this:

    features/support/paperclip/band/demo_tape.mp3
    features/support/paperclip/band/demo_tape.aac
    features/support/paperclip/band/demo_tape.ogg
    features/support/paperclip/band/demo_tape.wav
    features/support/paperclip/user/avatar.png
    features/support/paperclip/user/avatar.jpg
    features/support/paperclip/user/avatar.gif

This allows us to test the expected and unexpected formats by changing this
line:

```cucumber
When I attach a "demo_tape" "mp3" file to a "band" on S3
```

## Factories

The reason the `stub_paperclip_s3` and
`paperclip_fixture` methods are set up as a custom shoulda macro is
so that you can use them in your factory code:

```ruby
Factory.define :band_with_demo_tape, :parent => :band do |band|
  band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
end
```

## Go or no go

It's been quite useful so far for us. How are you testing Paperclip uploads to
S3? Do you see any way to improve this step definition or the convention?

* * *

**Note:**

Looking for FactoryGirl? The library was renamed in 2017.
[Project name history can be found here.](https://github.com/thoughtbot/factory_bot/blob/master/NAME.md)
