Rails 5: Matching Asset URLs in Feature Specs

As part of an ongoing Rails Upgrade for a large web application, I needed to update a feature spec that checked the URL of included assets. In earlier versions of Rails, these URLs could simply be checked by calling:

expect(find('img.my_asset_image')[:src]).to end_with('/my-image.png')

This expectation would pass given the following ERB output:

# <%= image_url('my-image.png') %>
# => <img src="/assets/my-image.png" />

In Rails 5, though, assets are compiled and fingerprinted during the test runs. This resulted in the generated URL containing a random fingerprint that could not be determined until runtime:

# <%= image_url('my-image.png') %>
# => <img src="/assets/my-image-aa00bb11cc22dd33ee44ff55.png" />

Writing a Simple Custom Matcher

To resolve this issue, I ended up writing a small custom matcher for RSpec that matched the URL against a simple regular expression.

# spec/support/custom_matchers.rb
RSpec::Matchers.define :eql_asset_url do |asset_name|
  match do |actual|
    pattern = asset_matcher_pattern(asset_name)
    expect(actual).to match(pattern)
  end

  failure_message do |actual|
    pattern = asset_matcher_pattern(asset_name)
    "expected #{actual} to contain an image asset url matching #{pattern}"
  end

  def asset_matcher_pattern(asset_name)
    # Split the asset's path, filename and extension for matching
    pathname = Pathname.new(asset_name)
    dirname = pathname.dirname.to_s
    dirname = '' if dirname == '.'
    extname = pathname.extname.to_s
    basename = pathname.basename.to_s.gsub(extname, '')

    %r{/assets#{dirname}/#{basename}-[a-f0-9]*#{extname}}
  end
end

Now in my feature specs, I can use the eql_asset_url matcher to test generated asset URLs:

expect(find('img.my_asset_image')[:src]).to eql_asset_url('my-image.png')