rspec-rails: Default_url_options is being ignored

In my rspec_helper.rb I defined;

Rails.application.routes.default_url_options[:host] = 'lvh.me'

It works fine for non-feature specs but doesn’t work for feature specs. Host for urls are equal to “www.example.com” in feature specs.

I’ve debugged and in feature_example_group.rb file, default_url_options is always an empty hash.

https://github.com/rspec/rspec-rails/blob/master/lib/rspec/rails/example/feature_example_group.rb#L18

found the following solution for this issue; https://github.com/beydogan/rspec-rails/commit/b590c82573b27bd8a1be967811b4835119260f09

Is this valid?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 18
  • Comments: 36 (13 by maintainers)

Commits related to this issue

Most upvoted comments

For future reference: I tried all sorts of variations (e.g config.action_mailer.default_url_options = { host: 'www.example.com' }, Capybara’s host etc), nothing worked. I then added Rails.application.routes.default_url_options[:host] = 'www.example.com' after the end (end of file, outside the config block) in config/environments/tests.rb and it worked.

Regardless of the last comments. I think we can close this issue. Feel free to comment if we think we should still look at it.

I think this is just a Rails issue. There are many places to set default_url_options, but they either work on console but not web, or the other way around:

In Rails 5.1.4, I have tested the following scenarios on web and console (but not in Rspec):

    # in development.rb
    config.action_controller.default_url_options({:protocol => 'https'})
    config.action_controller.default_url_options(:protocol => 'https')
    # Does not work

    # in development.rb, outside config block
    Rails.application.routes.default_url_options[:protocol] = 'https'
    # Does not work, but works under console

    # in routes.rb
    Rails.application.routes.draw do
      default_url_options protocol: :https
    # Does not work, but works under console

    # in ApplicationController
    def default_url_options
      { protocol: :https }
    end
    # Works in browser, but does not work under console

    # in development.rb
    config.action_controller.default_url_options= {:protocol => 'https'}
    # Works in browser, but does not work under console

For controller tests inside the Rails.application.configure block seemed to work for me:

  config.action_controller.default_url_options = {
    host: ENV['HOST']
  }

Any fixes for this problem ?

Ok, I dug into this a bit. It’s a little more complicated that it seems. Here’s what is going on.

The Rails test cases, the RSpec example groups are a thin shim on top of these, setup an attribute default_url_options. If this is not set or defined, then the call chain falls back on whatever the current routes scope has configured. By default it has nothing configured for :host and the value is nil.

If we removed or comment out the line:

default_url_options[:host] ||= ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST

You would get the following error:

Missing host to link to! Please provide the :host parameter, set
default_url_options[:host], or set :only_path to true

The reason your proposed solution in #1276 appears to work is simply because the example’s default_url_options is nil and so it falls back to the routes which you have customized in the spec/rails_helper.rb. For other spec types, which don’t have a :host option set by default, the route generation falls back to the session set by Rails’ test case. This depending on the spec type and is normally set to a default of www.example.com or pulled from a request, but is all handled by Rails’ test cases. This is not set for feature specs, because the session is handled by Capybara which does not set something for it.

So unfortunately, your solution is basically a no-op and is the same as deleting that if block. For the majority of cases, people don’t customize the host option and they would get the above exception.

We could change it to:

if respond_to?(:default_url_options)
  default_url_options[:host] ||= (
    app.routes.default_url_options[:host] ||
    ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST
  )
end

However, this will cause other problems:

require 'rails_helper'

Rails.application.routes.default_url_options[:host] = 'lvh.me'
RSpec.feature "Relative path hosts set by Capybara", type: :feature do
  it "causes a mismatch between the URLs" do
    visit widgets_path
    expect(current_url).to eq widgets_url
  end
end

Results in a failure:

Failures:

  1) Relative path hosts set by Capybara causes a mismatch between the URLs
     Failure/Error: expect(current_url).to eq widgets_url

       expected: "http://lvh.me/widgets"
            got: "http://www.example.com/widgets"

       (compared using ==)
     # ./spec/features/capybara_mismatch_spec.rb:7:in `block (2 levels) in <top (required)>'

This is because Capybara uses Capybara.default_host to generate the “full” URL under the hood. The default host just happens to be “www.example.com”. However, it won’t actually use that web server. It just uses the value to generate the full URL while still using it’s local server. You have to configure Capybara to use an external app host.

There’s another wrinkle. Capybara expects the “http://” part in the URL, if it’s not present the URL is created as: “http:///widgets”. I’m not sure this is a bug, you’d have to ask them. So while Rails is happy to have “lvh.me”, Capybara would need “http://lvh.me”.

We are tightly coupled to Capybara right now. As you can see from the feature example group file it’s essentially just all code necessary to handle Capybara’s design decisions. It also causes a lot of confusion for newer users who do not understand this tight dependency.

I really do not want to add yet another special case for Capybara. Let me think on this a bit. I have a potential solution in mind but will need to think on it for a day.

I hope this helps understand what’s happening.

If you set config.action_controller.default_url_options in config/environments/test.rb to the correct value, you can reuse it in feature specs by setting self.default_url_options = ... in a before block as below:

# File spec/rails_helper.rb
RSpec.configure do |config|
  config.before(type: :feature) do
    self.default_url_options = ApplicationController.default_url_options
  end
end

This fixes the issue originally described.

Same issue. Alternate resolution.

I noticed that a random feature test was failing inconsistently in both dev machine and CI environments. After reproducing with rspec --seed and getting consistent failures tried many of the approaches here. Unfortunately, none of them quite worked for me. The route helpers would still resolve incorrectly. i.e. my_page_url = www.example.com/my_page.

The interesting thing was that only the first feature test would fail. For all the subsequent feature tests in any feature testing file, the URL helpers would still resolve incorrectly, but the visit would be successful. In other words, visiting www.example.com/my_page would render my page correctly and run the necessary assertions as intended.

Simplest hack possible for me was just visiting one page before running the real tests:

  # HACK: https://github.com/rspec/rspec-rails/issues/1275
  config.before(:all, type: :feature) do
    visit(my_page_path)
  end

Added this to the RSpec configure block in my rails helper and haven’t had any problems since.

ruby 2.3.4p301 rails 5.1.1 rspec 3.6.0 capybara 2.14.0

I’m testing subdomains and instead of _path helpers using _url in my controller. It’s only what I set.