RSpec + Capybara + Devise: current_user? - ruby-on-rails

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.

Related

'assigns' method not found in rspec capybara

I have the following code in my controller:
private
def remaining_words
#remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
#questions_remaining = #remaining_words.length - 4
#quiz_words = #remaining_words.shuffle.take(4)
And here is my test:
feature 'Quiz functionality' do
scenario "gets 100% questions right in quiz" do
visit(root_path)
visit(start_quiz_path)
assigns(:questions_remaining).length.to_i.times do
orig_value = find('#orig', visible: false).value
choose(option: orig_value)
click_on('Submit')
expect(page).to have_content('You got it right!')
expect(page).not_to have_content('Sorry, wrong answer!')
end
expect(page).to have_content("Your score is 27/27")
save_and_open_page
end
end
I get the error message when I run the test:
NoMethodError: undefined method `assigns' for #<RSpec::ExampleGroups::QuizFunctionality:0x007f8f2de3f2b0>
# ./spec/features/quizzes_spec.rb:9:in `block (2 levels) in <top (required)>'
I've also tried using controller.instance_variable_get(:remaining_words) and get this error message
NameError:
undefined local variable or method `controller' for #<RSpec::ExampleGroups::QuizFunctionality:0x007fc4b99251a0>
Am I missing something in setting up the test? Should I be using describe instead of feature to enable the assign method?
assigns was solely available in controller tests - it was depreciated in Rails 5.
Testing what instance variables are set by your controller is a bad
idea. That's grossly overstepping the boundaries of what the test
should know about. You can test what cookies are set, what HTTP code
is returned, how the view looks, or what mutations happened to the DB,
but testing the innards of the controller is just not a good idea.
- David Heinemeier Hansson
In RSpec controller specs wrap the deprecated ActionController::TestCase.
A controller spec is identified by having the type: :controller metadata.
RSpec.describe ThingsController, type: :controller do
# ...
describe "GET #index" do
end
end
If you have set config.infer_spec_type_from_file_location! in config.infer_spec_type_from_file_location! RSpec will infer that any spec in spec/controllers has type: :controller.
You should avoid controller specs for new applications in favor of request and feature specs. One of the main problems with controller specs besides the violation of encapsulation is that the entire request phase is stubbed, the request does not actually go through rack or the routes which can mask routing errors and means that Rack middleware like Warden (used by Devise) or sessions must be stubbed.
If you have a legacy application you can reintroduce assigns with a gem. If you are just learning RSpec you should select more up to date tutorials.
Feature specs are high-level tests meant to exercise slices of
functionality through an application. They should drive the
application only via its external interface, usually web pages.
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/feature-specs
Use feature specs for high level tests centered on the user story. Use RSpec.feature "New Cool Feature" to write a feature spec.
Request specs provide a thin wrapper around Rails' integration tests,
and aredesigned to drive behavior through the full stack, including
routing (provided by Rails) and without stubbing (that's up to you).
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/request-specs/request-spec
Use RSpec.describe "Some resource", type: :request to write a feature spec.
Request specs are invaluable for testing API' or when you just need fast tests that ensure that the correct mutations happened to the DB or that the correct http responses are sent.
See:
https://blog.bigbinary.com/2016/04/19/changes-to-test-controllers-in-rails-5.html
https://github.com/rails/rails/issues/18950
You're writing feature specs/integration tests which don't have access to the controller/controller instance variables. They are meant to be more of a black box test executed from the users perspective. When setting up the data for the test you should know how many questions need to be asked and then either hardcode that in your test, or, better yet, detect based on the page contents whether there are more questions to answer (just like a user would have to).

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

ActiveRecord Objects created in Cucumber not showing in Rails web app

I am using cucumber (cucumber (1.1.0) cucumber-rails (0.3.2)) trying to test the web UI on a rails 2.3.14 application. The problem is that I am creating the user in the feature step:
visit("/")
#admin_user = Factory.create :admin_user
#admin_user.email
#admin_user.update_attributes :password => "testtest", :password_confirmation => "testtest"
#admin_user.register!
#admin_user.activate!
click_link("Login")
fill_in "Email:", :with => #admin_user.email
fill_in "Password:", :with => "testtest"
find("input[type=image]").click
But, when I visit the site and try to login the user is not visible in the database. Calling new_record? on #admin_user returns false but the rails application cannot see it. Any ideas on why this record is not showing up? I have confirmed that both cucumber and the rails application are point to the same database.
As #Ernest said, there are two connections. But turning off transations would slow down your suite, so use this code: https://gist.github.com/470808, and read: http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/ especially section 3.
It may happen if Cucumber and Rails are using two different connections. For that case, you may need to turn off transactions. More about the topic here: https://github.com/cucumber/cucumber/wiki/Browsers-and-Transactions
Are you sure they point at the same database? Normally cucumber will run in the test env and you visiting the site will either be in development or production environments.
If you have changed config/database.yml to point the test environment to the development or production database, Cucumber normally wraps each scenario in a transaction which rolls back.

Tests pass using "autotest" but not "rake test" using Authlogic

My tests fail when doing "rake test:functionals" but they pass consistently using autotest.
The failing tests in question seems to be related to Authlogic not logging in the user properly when using rake.
For facilitating signing in a user in tests, I have a test helper method as follows:
class ActionController::TestCase
def signin(user, role = nil)
activate_authlogic
UserSession.create(user)
user.has_role!(role) if role
end
end
The above method is used to signin a user
My stack is shoulda/authlogic/acl9/factory_girl/mocha
The reason why I suspect Authlogic being the issue is the failing tests look like this:
54) Failure:
test: A logged in user PUT :update with valid data should redirect to user profile. (UsersControllerTest)
[/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/action_controller/macros.rb:202:in `__bind_1251895098_871629'
/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `call'
/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `test: A logged in user PUT :update with valid data should redirect to user profile. ']:
Expected response to be a redirect to <http://test.host/users/92> but was a redirect to <http://test.host/signin>.
55) Failure:
test: A logged in user PUT :update with valid data should set the flash to /updated successfully/i. (UsersControllerTest)
[/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/assertions.rb:55:in `assert_accepts'
/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/action_controller/macros.rb:41:in `__bind_1251895098_935749'
/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `call'
/var/lib/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `test: A logged in user PUT :update with valid data should set the flash to /updated successfully/i. ']:
Expected the flash to be set to /updated successfully/i, but was {:error=>"You must be signed in to access this page"}
Autotest reads all test files upfront AFAIR (it does so with RSpec, I haven't been using plain tests for a long time now so I may be wrong).
To properly test controllers you need to call activate_authlogic in your setUp method. This is probably done automatically (globally) for integration tests.
Since autotest reads all tests it runs this global setUp and functional tests pass. When you run only functional tests authlogic is not enabled and your tests fail.
I'm not sure about where your problem lies, but I suggest you use Cucumber for testing controllers and user interaction instead of unit tests/rspec. The reason for that is that you exercise your entire app, including the authentication and authorization code you have.
Clearly the user is not getting logged in. Seems like Bragi Ragnarson might be on to something.
Here are some other things to isolate the problem:
Understand if the test is incomplete or relying on some side-effect of autotest. Run the test by itself:
ruby test/functionals/users_controllers_test.rb
Presumably that won't work. If it doesn't, there's some global code that is getting invoked for non functional tests by autotest. It's probably code setting in the test/integration or test/units directories, or one of their requires there.

Resources