reset_session not working in Cucumber tests - ruby-on-rails

I have the following step for a Cucumber scenario:
Given /^I am not logged in$/ do
request.reset_session
controller.instance_variable_set(:#_current_user, nil)
end
But when I run my Cucumber features, I get:
undefined method `reset_session' for nil:NilClass (NoMethodError)
My setup:
rails 3.0.3
cucumber-rails 0.3.2

Cucumber is intended to exercise the full stack. Given that, you generally don't want to muck around with the internals at a lower level as you might with rspec.
Try something like this:
Given /^I am not logged in$/ do
visit logout_path
end
This example uses Capybara's visit method, so you'll need to adjust accordingly, but this should give you the right idea.

Related

RSpec + Capybara + Devise: current_user?

rails (5.1.4)
rspec-rails (3.7.2)
capybara (2.16.1)
devise (4.3.0)
I'm trying to create a RSpec Rails 3.7 System spec as in https://relishapp.com/rspec/rspec-rails/v/3-7/docs/system-specs/system-spec .
Additionally I want to apply the Devise authorization. I followed the instruction https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
But I guess it's kind of a defective implementation. I don't get the current_user method (which is nil).
Here my simple spec:
require 'rails_helper'
RSpec.describe "testing system", type: :system do
it "tests the spec" do
admin = create(:admin) # user-admin
login_as(admin, :scope => :admin)
visit root_path
click_link 'Home'
save_and_open_page
end
end
Since the code on my site depends heavily on the current_user value, I don't get a properly formed saved page (it looks like a user did not log in).
What should I do? Should I try to simply manually (somehow?) assign a value to current_user? Or maybe the https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara strategy is mainly defective and should be substituted to something more full-fledged technology?
I already tried to make
current_user = admin
in my code - but with no result.
The devise wiki is correct for how to test while shortcutting logins, and no you can't just set current_user in your test code (nor can you access it since it's only set inside the request). Generally assume the error is in your usage of a gem, rather than a widely used and highly tested gem being defective.
With the description you've given there could be any number of reasons for it not actually logging in the user however the two most likely culprits are
Do you actually have multiple scopes configured in Devise? or should it just be login_as(admin, :scope => :user) # equivalent to just login_as(admin)
Is puma running your tests in single mode, or clustered mode? (You may need to disable the silencing of puma output rspec-rails does by default -https://github.com/rspec/rspec-rails/blob/95f4522c0549a32de59c19eb9b5f9a72126a9eb6/lib/rspec/rails/example/system_example_group.rb#L72 - to see that in the test output) If it's not in single mode then the app is being run in a separate process than the tests which means Devise can't actually shortcircuit the login, and you need to fix the puma config.

Faking instance variable in RSpec and Capybara feature spec

I'm trying to set up some feature specs before I get into refactoring some of my company's old code. It's kind of an unconventional setup, but I was able to figure out enough about test doubles to bypass the authentication enough to get started. One problem I'm still having is that some of the instance variables set in these methods I'm bypassing are expected by the view, so I get undefined method for nil:NilClass errors. I would like to get the specs running before I make any changes to the program code. In this case, I could easily just move the particular instance variable to another method. But I'm sure more situations like this will come up. Here's the example I'm currently working on:
def security_level
#right_now = Time.now
#
# other code that wont work without
# connecting to a remote authentication
# server
#
end
Then in my spec:
feature 'Navigation' do
before(:each) do
allow_any_instance_of(ApplicationController).to receive(:security_level).and_return(nil)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
Here's the error, coming from #right_now.year in the view
Failure/Error: visit root_path
NoMethodError:
undefined method `year' for nil:NilClass
# ./common/views/layouts/bootstrap/layout.haml:63
EDIT: Is there a way to set instance variables on the controller from within a feature spec?
There's no easy way to accomplish what you want.
The feature spec is handled mostly by Capybara, not RSpec. Capybara runs the majority of the browser / rails server behavior in an external process. This make it inaccessible from RSpec's point-of-view. Thus you cannot use stubs / doubles in this manner.
Feature specs are largely meant to be end-to-end acceptance tests. The idea is to exercise your system as those who would use your system do. Generally, in these types of specs you perform various "workflows". This means, having the spec, log a user in, navigate to particular pages, filling forms, clicking buttons and links. You then generally make your expectations on what you see in the view.
This means your spec would look more like:
feature 'Navigation' do
let(:regular_user) { User.create!(name: 'A Regular User') }
def sign_in(a_user)
visit sign_in_url
# fill out form
click_button 'Sign In'
end
before(:each) do
sign_in(regular_user)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
https://github.com/per-garden/fakeldap may provide enough ldap functionality for your feature tests.

Handle authentication with Capybara / Minitest for integration testing

I'm stuck trying to create integration tests using Capybara and MiniTest::Spec. I'm not using any 3rd party plugin for authentication. I'm using basic Authentication using has_secure_password built into rails 4.1
I have a helper that is looking for current_user which is created after authentication (pretty standard).
I've tried authenticating with Capybara then testing with visit:
test.rb
require 'test_helper'
describe "Admin area integration" do
setup do
def current_user
create(:admin_user, password: "test", password_confirmation: "test")
end
end
teardown do
current_user.destroy!
end
# results in error below
it "visits admin area path" do
visit admin_area_path
page.text.must_include('Dashboard')
end
# test passes
it "test user login" do
visit "/login"
within("#login_form") do
fill_in('email', with: current_user.email)
fill_in('password', with: "test")
end
click_button('login')
has_content?('Welcome')
end
end
Error
undefined method `email' for nil:NilClass app/helpers/application_helper.rb
Is there a way to pass the current_user object using capybara visit or am I missing something simple so the helper will not throw an error?
You are not supposed to modify internals of your Rails app, when doing integration tests. These tests should simulate the real world behaviour - a user visiting your site with a browser. So there is no way to pass the current_user object to capybara, like there is no way to modify the user session for your user from outside the app.
The straightforward way would be extracting the login steps(filling out the form) into separate function within some other test file( we usually have them all in test/support/** and just require all supporting functions in spec_helper). Then you repeat the login steps before any other test, which requires the user to be logged in.
However once we have tested the login, we can rely on it and the repetitive task of login the user each time can become quite annoying. It wouldn't be Ruby otherwise, when there wasn't a way to patch your app behaviour, while in test mode.
You can try using some mocking/stubbing lib and just stub the current_user method on any instance of the class which is holding it. Mocha example:
require 'mocha'
ApplicationController.any_instance.stubs(:current_user).returns(User.new {...})
The other option would be to modify the rack session directly. I expect your are storing the user_id in the session, and your current_user method just loads the user with that id.
So you can just require the rack_session_accessgem within your testsuite and set the user_id of your test user.
Remember also to disable transactional fixtures at least for the integration tests and use database_cleaner instead. Otherwise capybara will not be able to see any of your test data created, because it will be in an uncommitted transaction which is only accessible for the initiating thread.
See Configuring database_cleaner with Rails, RSpec, Capybara, and Selenium

testing rails app with cucumber and capybara won't recognize current_path.should == new_session_path

I'm trying to make a simple assertion on expected path.
in my step definition file, according to the capybara docs:
Then /^I should be on the login page/ do
current_path.should == new_session_path
end
this returns
undefined method `new_session_path' for #<Cucumber::Rails::World:0x0000010340b4c0> (NoMethodError)
It looks as though it's not loading the route helpers..
This was a brain-fart issue. Really sorry about that. A detail that eluded me for a long time was that I was running into rails' engine isolation issue. Specifically I was testing refinerycms and running into the problem described here: https://github.com/resolve/refinerycms/issues/1259
so to fix it was dead simple:
current_path.should == refinery.admin_root_path

Rails 3.1. Capybara's Selenium driver doesn't catch the routing error

When I use the visit method in Cucumber's step definitions and then run the step through Capybara's Selenium driver it's passed despite the controller isn't implemented.
Here's an example:
Feature
# features/one.feature
Feature: One
#selenium
Scenario: One
Given I visit the example page
Step definition
# features/step_definitions/example_steps.rb
Given /^I visit the example page$/ do
visit example_path
end
Route
# config/routes.rb
Example::Application.routes.draw do
resource :example
end
Controller
isn't implemented
Result
Feature: One
#selenium
Scenario: One
Given I visit the example page
1 scenario (1 passed)
1 step (1 passed)
0m18.162s
However, when I use the RackTest driver all works as it expected to be and the routing exception is risen unless a controller is implemented.
Here's the same example but with the usage of RackTest:
Feature
# features/one.feature
Feature: One
#rack_test
Scenario: One
Given I visit the example page
Result
Feature: One
#rack_test
Scenario: One
Given I visit the example page
uninitialized constant ExamplesController (ActionController::RoutingError)
./features/step_definitions/example_steps.rb:2:in `/^I visit the example page$/'
features/one.feature:5:in `Given I visit the example page'
Failing Scenarios:
cucumber features/one.feature:4 # Scenario: One
1 scenario (1 failed)
1 step (1 failed)
How can I force Capybara to raise the routing error when using the Selenium driver?
Thanks.
Ruby 1.9.2;
Ruby on Rails 3.1.0.rc1;
Cucumber 0.10.3;
Cucumber-rails 0.5.0;
Capybara 1.0.0.beta1;
Selenium-webdriver 0.2.0.
The Selenium driver does not fail when it receives a "failure" http code like 500. If you left your config/environments/test.rb at it's defaults, there should be a line config.action_dispatch.show_exceptions = false. So you can either set this to true or you would have to add some more steps to ensure the page is actually showing what you expect.

Resources