Ruby on Rails App Testing with Rspec and Capybara - ruby-on-rails-4

when i create a feature test for my application get the following error:
ActionController::RoutingError:
No route matches [GET] "/example"
My application uses subdomains and sub-applications(engines/modules) within these subdomains. Now when i set for Capybara the app_host or default_host through an feature_subdomain_helper like
Capybara.app_host = "example.lvh.me" or
Capybara.default_host = "example.lvh.me"
and into my rails_helper.rb i add the following code line
config.extend SubdomainHelpers, type: :feature
I get the same error. Now i think the configured subdomain are not considered by my feature test.
My Rspec Version is: 3.2
and Capybara Version is: 2.4.4
My sample feature test looks like:
require 'rails_helper'
feature 'Example Page' do
scenario 'visit example page' do
visit "/example"
expect(page).to have_content 'Example'
end
end
Have someone an idea what i do wrong?
Edit:
Mainapp routes:
constraints(Subdomain) do
mount Example::Engine => '/', as: 'example'
end
Engine routes:
Example::Engine.routes.draw do
scope '/example', nav_scope: 'example' do
end
end

The names of Capybara.default_host and Capybara.app_host are slightly misleading since they both need to be set as URLs to function properly
Capybara.default_host = "http://example.lvh.me"
If that doesn't fix your issue check rake routes and make sure the action you think is mounted at '/example' really is.

Related

ruby rails test case failing but real app works

So here's a strange problem: When I start my local rails app and browse to http://localhost:3000/static_pages/help I can see the page I created there.
However, the test case that I wrote says otherwise.
static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
end
test "should get help" do
puts static_pages_help_url
puts static_pages_help_path
get static_pages_help_url
assert_response :success
end
end
It fails with this error, Output of $bin/rake test:
Running:
..http://test.host/static_pages/help
/static_pages/help
E
Finished in 0.466745s, 8.5700 runs/s, 4.2850 assertions/s.
1) Error.
StaticPagesControllerTest#test_should_get_help:
ActionController::UrlGenerationError: No route matches {:action=>"http://test.host/static_pages/help", :controller=>"static_pages"}
test/controllers/static_pages_controller_test.rb:12:in `block in <class:StaticPagesControllerTest>'
Here is routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get "static_pages/help"
end
and here is the static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
end
and these two files
app/views/static_pages/home.html.erb
app/views/static_pages/help.html.erb
exist, as I can also see them when navigating to /static_pages/help in my browser. I've searched the web for hours, no clue.
$ rails --version
Rails 4.2.7.1
$ ruby --version
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
I must be missing something. Please help.
Since you're writing a controller spec, the parameter to a GET should be the action(controller method). But you're passing a URL. If you look at the error message, you can find that "http://test.host/static_pages/help" was passed into action. So, pass the name of the controller method as a symbol rather than the URL. Try
get :help
Note that help is the controller action.
However if you're interested in writing an integration test, you should inherit from ActionDispatch::IntegrationTest rather than ActionController::TestCase. So, your spec should look aomething like this.
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
end
To learn more about integration and controller tests, see http://weblog.jamisbuck.org/2007/1/30/unit-vs-functional-vs-integration.html
Hope this helps!

No route matches {:action=>"show" ... missing required keys: [:id]

Seeing many question related to this but none of them gives answer to my problem.
I have Rails api application without ActiveRecord support. It is easy to reproduce problem. Follow steps:
Create rails api site without ActiveRecord support
rails new test001 -O --api
Change folder to test001 and run:
rails g scaffold testapp id name
Create model file testapp.rb in app/models folder
class Testapp
include ActiveModel::Model
attr_accessor :id, :name, :created_at, :updated_at
def self.all
t1 = Testapp.new(:id =>111, name: "t111")
return [t1]
end
end
Start server
rails s
Using postman REST client create GET request
http://localhost:3000/testapps.json
It fails with error ActionView::Template::Error (No route matches {:action=>"show", :controller=>"testapps", :format=>:json, :id=>#<Testapp:0x00000005411518 #id=111, #name="t111">} missing required keys: [:id]):
I have dummy implementation for POST, PUT, GET 1 item and all works. Here is dummy implementation of GET 1 item (/testapps/x.json)
def self.find(p)
return Testapp.new(:id =>p, name: "t123")
end
What is the problem with GET all (/testapps.json)?
Found solution.
Problem is scaffold generated index.json.jbuilder
file:
json.array!(#testapps) do |testapp|
json.extract! testapp, :id, :id, :name
json.url testapp_url(testapp, format: :json) #REMOVE
end
It added line json.url testapp_url(testapp, format: :json) for no reason. json.extract! deserialized object already.
Removing line solved problem.
I still do not know why testapp_url(testapp, format:json) caused error. Checking Rails Routing document http://guides.rubyonrails.org/routing.html

Rails Domain Constraints ( to serve multiple domains )

$ rails -v Rails 4.2.1
$ ruby -v ruby 2.2.2p95 (2015-04-13 revision > 50295) [x86_64-linux]
I am building an API for a mobile app, which will have an admin interface to it. What i'm attempting to do, is run this through nginx using unicorn ( which I have running on my dev environment)
I have 2 domains routed to the exact same rails project. These domains are: api.project.dev and admin.api.project.dev
I've read this:
http://guides.rubyonrails.org/routing.html#advanced-constraints
and tried:
Separate Domain for Namespaced Routes in Rails 4 ( see answer )
I've tried a few other things to try and get this to work, the only thing that comes up ( for either sub-domain ) is:
Invalid route name, already in use: 'root'
My current implementation of this is:
class DomainConstraint
def initialize(domain)
#domains = domain
end
def matches?(request)
#domains.include? request.domain
end
end
and
require 'domain_constraint'
Rails.application.routes.draw do
resources :statuses
constraints (DomainConstraint.new('api.project.dev')) do
root :to => 'statuses#index'
end
constraints(DomainConstraint.new('admin.api.project.dev')) do
root :to => 'statuses#new'
end
end
keep in mind that the roots are different pages only for now, but ultimately will be completely different systems.
Not quite sure where to go from here in order to get this functioning as I would hope.
With the fine help of the great people in #RubyOnRails on irc I got this figured out. So thanks crankharder and sevenseacat for your input and advice.
What I ended up with was this:
class DomainConstraint
def initialize(domain)
#domains = domain
end
def matches?(request)
#domains.include? request.host
end
end
and:
require 'domain_constraint'
Rails.application.routes.draw do
constraints DomainConstraint.new('api.project.dev') do
resources :statuses
root :to => 'statuses#index', as: 'api_root'
end
constraints DomainConstraint.new('admin.api.project.dev') do
resources :statuses
root :to => 'statuses#new'
end
end
You can also constrain a route based on any method on the Request object that returns a String. http://guides.rubyonrails.org/routing.html#request-based-constraints
The methods available to Request include host, which could be used as follows:
constraints host: 'api.project.dev' do
resources :statuses
root to: 'statuses#index', as: 'api_root'
end
constraints host: 'admin.api.project.dev' do
resources :statuses
root to: 'statuses#new'
end

Capybara Poltergeist feature test failing on CI but passes locally

I'm having weird issues with some of my feature tests using Capybara with poltergeist driver.
The test should perform a simple checkout in my online shop.
They all pass fine on my local MacBook as well as on an Ubuntu vagrant box. However on CI services like Codeship, Wercker or Semaphore they fail with the very same error.
My spec:
require 'rails_helper'
describe 'Checkout' do
let!(:product) { FactoryGirl.create(:product) }
it 'checks out via CreditCard', js: true do
visit products_path
expect(page.body).to have_link('Test Product 1')
click_link('Test Product 1')
#rest of spec ommitted
end
end
The error I get on CI is:
2) Checkout checks out via CreditCard
Failure/Error: click_link('Test Product 1')
Capybara::ElementNotFound:
Unable to find link "Test Product 1"
To me this is super weird, as the first expectation 'expect(page.body).to have_link('Test Product 1')' seems to pass but then it fails on the next step where it should actually click the link it just assured to be present on the page?
I then reconfigured poltergeist driver as follows to gather more debug information.
Snippet of rails_helper.rb:
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, {js_errors: false,
#inspector: true,
phantomjs_logger: Rails.logger,
logger: nil,
phantomjs_options: ['--debug=no', '--load-images=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1'],
debug: true
})
end
Capybara.server_port = 3003
Capybara.app_host = 'http://application-test.lvh.me:3003' # lvh.me always resolves to 127.0.0.1
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = :poltergeist
Capybara.default_wait_time = 5
Now I can see on CI console that the test successfully visits my products_path and the expected html page (including my the link it should click) is being returned.
I removed the rest of the HTML response to make it more readable:
{"name"=>"visit", "args"=>["http://application-test.lvh.me:3003/products"]}
{"response"=>{"status"=>"success"}}
{"name"=>"body", "args"=>[]}
{"response"=>"--- snip --- <div class=\"info\">\n<a class=\"name color-pomegranate\" href=\"/en/products/6\">\nTest Product 1\n</a>\n850,00 \n</div> --- snap ---"}
{"name"=>"find", "args"=>[:xpath, ".//a[./#href][(((./#id = 'Test Product 1' or normalize-space(string(.)) = 'Test Product 1') or ./#title = 'Test Product 1') or .//img[./#alt = 'Test Product 1'])]"]}
{"response"=>{"page_id"=>4, "ids"=>[0]}}
{"name"=>"visible", "args"=>[4, 0]}
{"response"=>false}
{"name"=>"find", "args"=>[:xpath, ".//a[./#href][(((./#id = 'Test Product 1' or contains(normalize-space(string(.)), 'Test Product 1')) or contains(./#title, 'Test Product 1')) or .//img[contains(./#alt, 'Test Product 1')])]"]}
{"response"=>{"page_id"=>4, "ids"=>[1]}}
{"name"=>"visible", "args"=>[4, 1]}
{"response"=>false}
The last two find actions repeat until Capybara reaches its timeout, then the test fails.
I double checked the xpath Capybara uses via some online xpath validators, but as expected it matches the HTML link.
I also used capybara-screenshot gem to dump the HTML body on failure and the link in question is also present.
So why is the test still failing?
Is there any race condition that I'm not aware of? Why is it passing locally but on none of the CI services?
Here are my gem version:
capybara (2.4.4)
capybara-screenshot (1.0.3)
database_cleaner (1.3.0)
factory_girl (4.5.0)
factory_girl_rails (4.5.0)
poltergeist (1.5.1)
rails (4.1.8)
rspec (3.1.0)
rspec-rails (3.1.0)
and phantomjs 1.9.7
While I can't reproduce this, I remember having this problem before. I believe your line:
expect(page.body).to have_link('Test Product 1')
is passing because the link is literally on the body of the html page, even though it may be hidden due to CSS or JS behavior. However, the line:
click_link('Test Product 1')
definitely checks for visibility before clicking the link. You should check your spec_helper.rb configurations to make sure:
Capybara.ignore_hidden_elements = true
is present, so that the first line wouldn't pass. I think I also had to change the first line I mentioned to:
# Change page.body to page, to look at the rendered page, not the literal one
expect(page).to have_link('Test Product 1')
Once you do this, the first line blocks the thread and waits until the link becomes visible. Then the rest of the test will pass.
Hope this solves it.

"undefined method `env' for nil:NilClass" in 'setup_controller_for_warden' error when testing Devise using Rspec

I'm trying to create a spec for a sign out flow by using factorygirl to create a user and then use Devise's sign_in method to authenticate the user, then use capybara to click the "Sign Out" link.
I'm getting (what seems to me to be) a strange error when I run the spec:
Failures:
1) Sign out flow successfully redirects to the welcome index (root)
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `env' for nil:NilClass
# /home/vagrant/.rvm/gems/ruby-2.0.0-p576/gems/devise-3.4.1/lib/devise/test_helpers.rb:24:in `setup_controller_for_warden'
Finished in 0.00226 seconds (files took 3.32 seconds to load)
1 example, 1 failure
Here's the spec:
require 'rails_helper'
describe "Sign out flow" do
include Devise::TestHelpers
describe "successfully" do
it "redirects to the welcome index (root)" do
user = create(:user)
sign_in user
within '.user-info' do
click_link 'Sign Out'
end
expect(current_path).to eq root_path
end
end
end
And my user.rb factory:
FactoryGirl.define do
factory :user do
name "Fake User"
sequence(:email, 100) { |n| "person#{n}#example.com" }
password "helloworld"
password_confirmation "helloworld"
confirmed_at Time.now
end
end
The error seems to be triggered simply from the line include Devise::TestHelpers, as I've tried commenting out the entire content of the spec and still get the same error.
I thought the Devise test helpers would work out of the box; did I miss some configuration? Thanks.
In Rails 5 you must include Devise::Test::IntegrationHelpers instead Devise::Test::ControllerHelpers:
# rails_helper.rb
config.include Devise::Test::IntegrationHelpers, type: :feature
See more:
https://github.com/plataformatec/devise/issues/3913#issuecomment
https://github.com/plataformatec/devise/pull/4071
Apparently there are issues with Devise::TestHelpers and integration testing, so perhaps that's the problem here.
https://github.com/plataformatec/devise (mentioned in README, Issues, etc.; also see related SO questions):
These helpers are not going to work for integration tests driven by Capybara or Webrat. They are meant to be used with functional tests only. Instead, fill in the form or explicitly set the user in session;
FWIW it seems like the issues have been fixed, however I ran into the issue after not reading the documentation well enough.
This was our code:
RSpec.configure do |config|
...
config.include Devise::TestHelpers
...
end
This means every test will include the test helpers, including models. This wound up being the issue for us. Should we have read the documentation closer we would have noticed Devise suggests limiting it to only controllers with:
RSpec.configure do |config|
...
config.include Devise::TestHelpers, type: :controller
...
end
This solved the issue for us. All tests passing :)
Here's my solution:
class ActiveSupport::TestCase
# all the normal stuff
end
class ActionController::TestCase
include Devise::TestHelpers
end
I meet the same error on rails 5.
Here's my solution
spec/rails_helper.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.include Devise::TestHelpers, type: :view
config.include Warden::Test::Helpers
end
spec/controllers/your_controller_spec.rb
RSpec.describe YourController, type: :controller do
before(:all) do
user = FactoryGirl.create(:user)
login_as user, scope: :user
end
it "#index" do
get "index"
expect(response).to render_template(:index)
expect(response).to have_http_status(200)
end
$ rspec --tag focus
Run options: include {:focus=>true}
DashboardController
#index
Finished in 3.9 seconds (files took 3.5 seconds to load)
1 example, 0 failures
Like others have already said, you're including the Devise::TestHelpers. That's for testing controllers. If you'd still like to automatically login a test user in your integration tests, check out the official Devise Instructions on using it with Capybara.
Using Devise with Capybara
Basically, what you need to do is first enable Warden's test mode:
include Warden::Test::Helpers
Warden.test_mode!
Then, (create and) login your user:
user = FactoryGirl.create(:user)
login_as(user, scope: :user)
Example:
# spec/features/survey_spec.rb
require 'rails_helper'
feature 'survey app' do
include Warden::Test::Helpers
let(:user) { create(:user) }
let(:survey) { create(:survey_with_questions) }
before do
# Sign the User in
Warden.test_mode!
login_as(user, scope: user)
end
it 'renders the survey' do
visit survey_show_path(survey)
expect(page).to have_content(survey.title)
end
end
I was having this problem when trying to sign_in a user in a before hook:
before(:context) do
create(:skill, name: 'Google Maps API'.downcase)
user = create(:user)
sign_in user
end
Placing sign_in inside the before hook leads to:
Failure/Error: sign_in user
NoMethodError:
undefined method `env' for nil:NilClass
But placing it inside an example works fine:
shared_examples_for('an authenticated resource.') do
describe 'An authenticated request' do
it "responds with HTTP status OK" do
user = create(:user)
sign_in user
make_request
expect(response).to have_http_status(:ok)
end
end
end
But this can be improved, placing the sign_in into a before(:example) that will also work:
context 'allow search by keyword' do
let!(:skill){ create(:skill, name: 'Google Maps API'.downcase) }
let!(:user) { create(:user) }
before(:example) { sign_in user }
it 'finds matching records' do
get :search, name: "Google Maps API", format: :json
expect(assigns(:skills).size).to be(1)
end
it 'finds records that start with keyword'
it 'finds records that end with keyword'
it 'finds records that contains keyword'
end
My Devise version is 4.2.0 so I just included
config.include Devise::Test::ControllerHelpers, type: :controller
in my rails helper file.
Alternatively you can use the same in your spec as
include Devise::Test::ControllerHelpers
The correct syntax for Rails 5 / Devise (4.2.0) is
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, :type => :controller
end
Devise::TestHelpers are deprecated so use Devise::Test::ControllerHelpers
:type => :controller - to limit only for
controllers and not models for example.
For the sake of being complete, with Rails 5 and RSpec I have run into similar issues when using the latest helpers, as they need to be set explicitly with the type when not used as a superclass.
So if you find yourself receiving there errors in your model tests there's a pretty good chance the type is not set.
Here's what I use in the spec_helper:
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::IntegrationHelpers, type: :feature
I know that the docs do mention this, but there are times when you may run across an older blog that gives you an older approach, or upgrading from an older setup, and next thing you know this happens.