After posting a tweet about using fixtures a while ago, @stravid replied and asked me for some pointers to using fixtures in CarrierWave, the Rails file uploading gem, so I decided to dive in and see if I could get it to work. Here's how I would do it.
When using CarrierWave you create an uploader, which subclasses from
CarrierWave::Uploader::Base, add a field to the database that holds the upload's filename, and link it all together using a call to
mount_uploader in the model. So, when uploading avatars for users, for example, you'd create an
AvatarUploader (there's a generator for that), add a string column in your users table named "avatar", and use it in your model like this:
# app/models/user.rb class User < ActiveRecord::Base mount_uploader :avatar, AvatarUploader end
Now, how would we test this? Let's put an image in
test/fixtures/files/tapir.jpg, and use
fixture_file_upload to test the uploader. Here's a test that checks if an existing user has an avatar, and one to make sure an avatar can be created with a new user:
# test/models/user_test.rb require_relative '../test_helper' class UserTest < ActiveSupport::TestCase test "has an avatar" do user = users(:user_with_avatar) assert File.exists?(user.avatar.file.path) end test "uploads an avatar" do user = User.create!(:avatar, fixture_file_upload('/files/tapir.jpg', 'image/jpg')) assert(File.exists?(user.reload.avatar.file.path)) end end
The first test uses a fixture named
users(:user_with_avatar), so let's create that first.
When you upload a file, only its basename gets stored in the
User#avatar field, and the rest of the path to the file comes from your uploader class, meaning a fixture would look like this:
# test/fixtures/users.yml user_with_avatar: # generated id: 605975481 avatar: 'tapir.jpg'
Now, if you want to get "uploaded" files from the fixtures directory instead of having to upload it before every test, you can change
CarrierWave.root in your test helper so it points to the fixture directory instead of the project's public directory:
# test/test_helper.rb CarrierWave.root = 'test/fixtures/files'
Then, putting a file in
test/fixtures/uploads/user/avatar/605975481/tapir.jpg (where "605975481" is the user's autogenerated ID), will make sure CarrierWave can find the fixture user's avatar in your tests.
Both tests should pass right now, but there's a problem. The second test uploads a new file directly to the fixture directory, which is not where you want it. What you actually want is to save the uploaded files to a temporary location, so the files created by your tests won't make a mess out of your fixture files.
After looking through CarrierWave's source for a while, I found that it actually already does this. When you upload a file and don't save it, CarrierWave will keep it in a temporary files directory until you save the parent model instance, which then moves the file to your uploader's
store_dir and removes the temporary version.
So, as long as you don't actually move the file to its final location (which, in your case is the fixture directory), CarrierWave will simply keep using the cached file path. If you break
CarrierWave::Mount::Mounter#store! in the test helper, you'll make sure nothing ever actually gets stored while running your tests:
# test/test_helper.rb ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class CarrierWave::Mount::Mounter def store! # Not storing uploads in the tests end end class ActiveSupport::TestCase include ActionDispatch::TestProcess fixtures :all CarrierWave.root = Rails.root.join('test/fixtures/files') def after_teardown super CarrierWave.clean_cached_files!(0) end end
Running the tests again, you'll see both tests still pass. The first test loads the file from the fixture directory we created, and the second one uploads a new file to
test/fixtures/files/uploads/tmp, which is a path you can easily
.gitignore. Also, there's an
after_teardown to clean up cached files. We're passing a 0 because CarrierWave defaults to cleaning files that are at least one day old, and we want to remove everything all the time.
I've created a demo Rails project (diff), so you can play around with this yourself. Also, I've submitted a patch to CarrierWave to add a
:cache_only option, which would save you some monkey-patching. It's merged in, but it hasn't been released yet, so you'll have to use the edge version of CarrierWave if you want to try it.
If you tried this approach in your project and have anything to add, please let me know!