navbar_item does not accept a link in test environment with rspec
joelklint opened this issue · 7 comments
Problem
navbar_item can not receive a link without throwing an error in the test environment. It does not matter if the link is generated with the '[input destination here]_path' helper or if it is hard coded.
Notice that navbar_header can receive a 'brand_link' without any trouble.
Link to repository reproducing the error
My navbar code
# app/views/layouts/application.html.erb
<%= navbar do %>
<%= navbar_header brand: 'Home', brand_link: root_path %>
<%= navbar_collapse do %>
<%= navbar_group do %>
<%= navbar_item "Root", root_path %>
<% end %>
<% end %>
<% end %>
Problem arises during a view spec.
When rendering takes place, the following is printed in terminal
Failure/Error: <%= navbar_item "Root", root_path %>
ActionView::Template::Error:
bad URI(is not URI?): http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:17:in `block (3 levels) in _app_views_layouts_application_html_erb__4503369544053172873_70210535674700'
# ./app/views/layouts/application.html.erb:16:in `block (2 levels) in _app_views_layouts_application_html_erb__4503369544053172873_70210535674700'
# ./app/views/layouts/application.html.erb:15:in `block in _app_views_layouts_application_html_erb__4503369544053172873_70210535674700'
# ./app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb__4503369544053172873_70210535674700'
# ./spec/views/layouts/application.html.erb_spec.rb:5:in `block (2 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# URI::InvalidURIError:
# bad URI(is not URI?): http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:17:in `block (3 levels) in _app_views_layouts_application_html_erb__4503369544053172873_70210535674700'
Tracing it to this gem
To trace the problem to this gem, i did two things.
- Replaced 'root_path' with hard coded path to rule out the '[input destination here]_path' helper
# app/views/layouts/application.html.erb
<%= navbar do %>
<%= navbar_header brand: 'Home', brand_link: root_path %>
<%= navbar_collapse do %>
<%= navbar_group do %>
<%= navbar_item "Root", "/test" %>
<% end %>
<% end %>
<% end %>
This failed, with the following stack trace
Failure/Error: <%= navbar_item "Root", '/test' %>
ActionView::Template::Error:
bad URI(is not URI?): http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:17:in `block (3 levels) in _app_views_layouts_application_html_erb__2238483034105279321_70189671204360'
# ./app/views/layouts/application.html.erb:16:in `block (2 levels) in _app_views_layouts_application_html_erb__2238483034105279321_70189671204360'
# ./app/views/layouts/application.html.erb:15:in `block in _app_views_layouts_application_html_erb__2238483034105279321_70189671204360'
# ./app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb__2238483034105279321_70189671204360'
# ./spec/views/layouts/application.html.erb_spec.rb:5:in `block (2 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# URI::InvalidURIError:
# bad URI(is not URI?): http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:17:in `block (3 levels) in _app_views_layouts_application_html_erb__2238483034105279321_70189671204360'
- Removed the URL from the 'navbar_item' and placed it by itself.
# app/views/layouts/application.html.erb
<%= navbar do %>
<%= navbar_header brand: 'Home', brand_link: root_path %>
<%= navbar_collapse do %>
<%= navbar_group do %>
<%= navbar_item "Root" %>
<% end %>
<% end %>
<% end %>
<%= root_path %>
This worked without any errors.
The test
# spec/views/layouts/application.html.erb_spec.rb
require 'rails_helper'
RSpec.describe "layouts/application", type: :view do
it "can render" do
render
end
end
Environment information
Tested on two different Operating Systems
- OS-1: OS X 10.11.5
- OS-2: macOS 10.12
Ruby and gem versions:
- Ruby: ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]
- Using rails 5.0.1
- Using bootstrap-sass 3.3.7
- Using bootstrap-navbar 2.3.0
- Using rails_bootstrap_navbar 2.0.1
- Using rspec 3.5.0
Hi Joel,
Thanks for the extensive info on the problem!
However, the fact that it only happens when running through Rspec is a hint that it's Rspec's fault. 😄
It seems like Rspec messes up request.original_url
which is used by this gem to check whether a navbar link should be marked as active.
I added a raise request.original_url
right above the navbar code in your application layout:
1) layouts/application can render
Failure/Error: <% raise request.original_url %>
ActionView::Template::Error:
http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2789497569415273900_70240385678020'
# ./spec/views/layouts/application.html.erb_spec.rb:5:in `block (2 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# RuntimeError:
# http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
# ./app/views/layouts/application.html.erb:12:in `_app_views_layouts_application_html_erb___2789497569415273900_70240385678020'
Note that request.original_url
is http://test.hostNo route matches {:action=>"application", :controller=>"layouts"}
, it should be something like http://test.host
.
Are you sure you can write a view spec for a layout like that?
Hi Manuel,
I have found a solution now. Simply mocking ActionController::TestRequest in view specs solved it.
before do
allow_any_instance_of(ActionController::TestRequest).to receive(:original_url).and_return('')
end
I have updated the repo, for anyone having the same issue in the future. https://github.com/JoelKlint/rails_bootstrap_navbar_issue
Thank you for the info regarding request.original_url
What's the purpose of testing if the application layout can render by itself anyways? 😄
Haha, that is not the test I am actually doing. I just made it as simple as possible for reproduction.
I am testing conditional rendering and if links to certain parts of my application exists
And if you test rendering a specific template, is the request.original_url
also set incorrectly? It might be that the No route matches {:action=>"application", :controller=>"layouts"}
only happens because you're trying to render the application layout and Rspec/Rails cannot find a route for it. Could you try testing to render a specific template and raise request.original_url
in the template or layout to see what it is set to?
I just tried raising request.original_url
in a regular view spec, and I too got the ActionView::Template::Error
.
It seems that on any view spec (view, layout, partial), request.original_url
is causing trouble.
In order to avoid any future problems, i have configured rspec to always mock ActionController::TestRequest
in view specs.
This is how I did it.
# spec/rails_helper.rb
require 'support/simplecov' # Must be loaded first in order to work
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'support/bootstrap-navbar' # import custom configuration in rails_helper.rb
# spec/support/bootstrap-navbar.rb
RSpec.configure do |config|
config.before :each do |test|
if test.metadata[:type] == :view
allow_any_instance_of(ActionController::TestRequest).to receive(:original_url).and_return('')
end
end
end
Looks good, but beware that in this case during testing no navbar items will ever be marked as "current".