I am trying to test that someone is able to login to my site by making a POST request to my SessionsController
. I've seen this way recommended in a few places:
it 'must be able to sign in a user' do
user = create(:user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
assert_response :success
@controller.current_user.must_equal user
end
But this test is not correct. Calling @controller.current_user
will attempt to authenticate the user using the posted parameters and will return user
if the supplied email/password is correct. There is no guarantee that the create
action is actually calling sign_in
or current_user
.
Even if I re-write the test to check that these methods are called, it's possible that other methods could be called e.g. sign_out
.
Is there a more definitive way to ultimately check if a user is logged in, and if so, who the user is?
EDIT -
For example, the following test will pass
it 'must sign in a user' do
@controller.current_user.must_equal nil
post :create, format: :js, user: {email: @user.email, password: @user.password, remember_me: 0}
assert_response :success
@controller.current_user.must_equal @user
end
when the SessionsController#create action is:
def create
respond_to do |format|
format.js {
render nothing: true, status: 200
}
end
end
You’re now able to run your app and it should include pages like /users/sign_up and /users/sign_in. In theory Devise should work now but, unfortunately, the view links and redirects used by Devise won’t work in Rails 7. Let’s fix that.
Next, you’ll want to create your user model with devise, similar to the way you would create a user model otherwise. Run rails g devise user in your command line. This will create a user migration as well as the user model, which you can configure the same way you would any other model.
We need to alter the code that Devise generates for us to deal with Turbo. So, once you’ve run rails generate devise:install we need to alter the Devise initializer config in several places beyond what the Devise README instructs us to do and add a controller as Devise’s parent controller.
Integrating Devise Auth with Rails 7 has some undocumented twists. Learn about them here or use the included template to bypass them
Solution with minimal changes to proposed code in the question:
You need to initialize the system before the test starts. Try prepending following code before your it 'must be able to sign in a user' do
code:
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
This should turn your test into a valid test for your post controller.
Explanation:
My assumption is, that your test above always succeeds, because the user is already signed in (by other tests run before this one). You could verify this by using byebug
in the line after it
and run current_user
in bybug's
console. If it is not nil
, the user is already signed in, which is invalidating your test.
Note, that (different from what is discussed above in the comments), current_user
does not change the status of the user; it is a read-only function.
Shorter/cleaner solution:
In my opinion, there is a a cleaner way to perform such a test like follows:
def sign_in_via_post(user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
end
...
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
it 'must be able to sign in a user' do
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
end
With the should change from nil to user
statement, you verify, that the user was logged out before the test begins and that the user is logged in, after the test has been performed.
Note, that the part
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
is equivalent to the (maybe easier to understand) code
{ sign_in_via_post user }.should change { user_signed_in? }.from(false).to(true)
as discussed here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With