RSpec::RemoteFixtures

RemoteFixtures is a plugin for RSpec that lets you store test fixture files in s3 to avoid unnecessary overhead.

Why would I ever want to use this gem?

This gem lets you stop committing large fixture files, without having to worry about git-lfs. Furthermore, a great many applications use docker images to run in production and CI. If you have hundreds of MB worth of fixture files, these files are first downloaded to wherever the image is being built, then uploaded, over the network, likely to many CI workers and production instances. Once the files are there, it's likely only a small proportion of these workers actually need any particular file.

Thus, rspec-remote-fixtures: A gem that sticks your rspec fixtures in S3, and downloads them transparently

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add rspec-remote_fixtures

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install rspec-remote_fixtures

You will likely want to add the gem to the develop and test groups in your application's Gemfile

Configuration

Create an initializer rspec_remote_fixtures.rb:

if Rails.env.test? || Rails.env.development?
  RSpec::RemoteFixtures::Config.backend_path = 's3://my-s3-bucket/some-prefix-path/'
  # the following are defaults, only set if you want to override them
  RSpec::RemoteFixtures::Config.manifest_path = 'spec/fixtures.json'
  RSpec::RemoteFixtures::Config.backend = RSpec::RemoteFixtures::Backend::S3Backend
  RSpec::RemoteFixtures::Config.fixture_path = Rails.root.join('spec/fixtures')
  # use to set/override s3 auth if you need different credentials for the above bucket
  RSpec::RemoteFixtures::Config.s3_client = Aws::S3::Client.new 

  # When should we validate the digest of a file fixture?
  # always: any time the file is used in a spec
  # download: whenever the file is downloaded to this runner for the first time
  # never: <--
  RSpec::RemoteFixtures::Config.check_remote_fixture_digest = :download
end

In your rails_helper.rb:

require 'rspec/rails'
# Important: we hook into some of the helpers provided by rspec/rails so this must come after requiring it:
RSpec::RemoteFixtures.setup_rspec!

After the gem is configured, you will want to generate a manifest of your existing fixtures:

rails generate rspec:fixture_manifest

If the s3 object in question has an etag which matches the local digest of the file, the file will not be re-uploaded.

Future files can be added or updated by calling the generator again with the --files parameter:

rails generate rspec:fixture_manifest --files spec/fixtures/bob.txt

Once the manifest has been set up, you can remove the fixture files from version control, and commit the manifest file.

Usage

There are two main ways to use this tool in your specs:

# fixture_file_path returns an absolute Pathname to a fixture located in RSpec::RemoteFixtures::Config.fixture_path
# The following invocation will ensure the file is present, 
# and return Pathname.new('/my/rails/root/spec/fixtures/bob.txt')
# This method is available to FactoryBot factories as well as RSpec examples.
fixture_file_path('bob.txt')

# fixture_file_upload hooks into rspec/rails's method of the same name, 
# downloading the file if not present and then calling super
fixture_file_upload('bob.txt')

Warnings

S3 authentication relies on an accurate date. If you are using Timecop, RemoteFixtures will attempt to unfreeze for the while downloading the file, and return, by calling Timecop.unfreeze in a block. Other libraries that freeze time may cause downloads to fail.

Design

RSpec::RemoteFixtures creates a manifest JSON file of the following form:

{
  "some/path.txt": {
    "digest": "d8e8fca2dc0f896fd7cb4cb0031ba249",
    "remote_path": "s3://some-bucket/prefix/d8e8fca2dc0f896fd7cb4cb0031ba249_path.txt"
  }
}

When fixture_file_path is called, the gem checks to see if the file is present on the local filesystem, and conditionally verifies the digest of the local copy (see Configuration section). If the file is not present, it retrieves the file, again potentially verifying the digest.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/aleksclark/rspec-remote_fixtures.