muck focking

Jared Carroll

Most of the time in my tests I mock out all external resources, e.g. file systems, network I/O, databases, etc. I recently discovered the #fixture_file_upload method that’s available in Rails tests. File upload was one area I always mocked out because I didn’t even know how to do a file upload in a functional test. With this helper method you can do regular state-based testing with file uploads.

Say you got a User who’s got an image along side all the normal User attributes.

Schema:

users (id, email, password)

We’ll store the image on the file system:

class User < ActiveRecord::Base
  def image=(image)
    file_name = File.join IMAGES_PATH, image.original_filename
    File.open(file_name, 'wb') do |file|
      file.puts image.read
    end
  end
end

Our UsersController looks the same as any other simple CRUD controller.

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new params[:user]

    if @user.save
      redirect_to user_path(@user)
    else
      render :action => :new
    end
  end
end

Now let’s take a look at our functional test.

def test_should_create_a_new_user_record_on_POST_to_create
  post :create, :user => {
    :email => 'pepper@boloco.com',
    :password => 'burritos',
    :image => fixture_file_upload('images/boloco.gif', 'image/gif') }

  assert File.exists?(File.join(IMAGES_PATH, 'boloco.gif'))
  assert_response :redirect
  assert_redirected_to user_path(assigns(:user))

  assert_recognizes({ :controller => 'users',
                      :action => 'create' },
                    :path => 'users', :method => :post)
end

The helper method #fixture_file_upload will look for the image file relative to ‘test/fixtures’. I decided to create an ‘images’ subdirectory there just to keep it straightforward (as long as I don’t have an Image model). The method takes a filename and its MIME type. After the POST I assert the file exists.

The IMAGES_PATH constant is environment specific and defined in ‘config/environments/test.rb’ as:

IMAGES_PATH = File.join RAILS_ROOT, 'tmp', 'images'

You might want to throw the following line in your functional test’s #setup method to clean up your IMAGES_PATH directory:

FileUtils.rm_rf Dir["#{IMAGES_PATH}/*"]

I don’t know if I’d ever write tests like this but mocking has been burning me lately a bunch so it’s good to know I can do state-based file upload testing.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.