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.