After posting a tweet about using fixtures a while ago, David Strauß 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:
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:
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:
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:
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!