Simple example of Rails Testing Devise Controllers - ruby-on-rails

I want the simplest example to test the validity of attributes of devise's actions for new registrations (email, password, password confirmation), sign_in and forgot_password. The internet is full of RSpec ones but I want to go native with what Rails 4.2 gives me and there is absolutely nothing.
I am stuck to the default implementation:
require 'test_helper'
class RegistrationsControllerTest < ActionController::TestCase
def setup
#controller = RegistrationsController.new
#request = ActionController::TestRequest.new
#response = ActionController::TestResponse.new
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Registrations.new(username: "John", email: "myemail#email.com")
end
end
I know this isn't too much but I am making my first steps in TDD, so please don't shoot !
How do I check for the validity of a user's attributes, for example a nil email or a password of 100 characters ?

You can check with rails in-build minitest
I have assumed you have overriding devise's registration_controller.
In tdd for controller you can write test cases for action.
Check modified code :-
require 'test_helper'
class RegistrationsControllerTest < ActionController::TestCase
include Devise::TestHelpers # for including devise's actions
def setup # this set up default settings for controller
#controller = RegistrationsController.new
#request = ActionController::TestRequest.new
#response = ActionController::TestResponse.new
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Registrations.new(username: "John", email: "myemail#email.com")
end
setup do # this used for setting global variable used in test file
#user= users(:one) # users is the fixture file of test in which you can set default data for test environment.
end
test "should create user" do # then you test cases for controller
sign_in users(:one)
post :create, users:{email:'test#test.com, password:'XXXX'...}# you can pass arguments for create method. Please check it once, i am not sure about names
assert_response :success
end
end
Rails minitest provides some assertion.

Related

setting up minitest with Devise with refactoring test_helper

Rails 7 application using devise for authentication.
fixture (model tests pass for all classes):
one:
email: 'me#mail.co'
encrypted_password: <%= User.new.send(:password_digest, '12345678')
test_helper which attempts to re-factor the login and action process into short one-liners (test_access) in the actual controller tests
require 'simplecov'
SimpleCov.start 'rails'
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require 'webmock/minitest'
require 'barby/outputter/png_outputter'
require 'barby/barcode/ean_13'
class ActiveSupport::TestCase
fixtures :all
include Devise::Test::IntegrationHelpers
include Warden::Test::Helpers
parallelize(workers: :number_of_processors)
def log_in(user)
if integration_test?
login_as(user, :scope => :user)
else
sign_in(user)
end
end
class Minitest::Test
def setup
#one = users(:one)
end
end
private
def test_access(user, action)
get '/users/sign_in'
puts user.inspect
sign_in user
# puts user_signed_in?
# puts current_user.manager?
puts user.manager?
post user_session_url
follow_redirect!
puts action
get action
assert_response :success
end
and the controller_test (presently on an intermediate refactoring level, as the method should extend to an array of users)
require "test_helper"
class LokationsControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
#users_allowed = [ #one ]
#actions = [dummy_lokations_url]
end
test "should get dummy" do
#actions.each do |action|
test_access(#one, action)
assert_response :success
end
end
the problem is there seems to be no way of verifying whether the user is signed in or not, notwithstanding the inclusion of devise integration helpers. The output in the console points to proper values, but the result is the opposite of the method that should be applied
#<User id: 760812109, email: [...]>
true
http://www.example.com/lokations/dummy
[...]
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/>
Response body: <html><body>You are being redirected.</body></html>
because the before_action method applied on the action specifies
def index_manager
if user_signed_in? && current_user.manager?
else
redirect_to root_path and return
end
end
Where is the above mistaken ? (I have a nagging doubt that something may be missing as this pattern has been successfully followed in the past)
Sorry but this is just not a good/acceptable refactor.
Do not set the password digest - ever. That should only be known by the underlying system that encrypts the password. Your code and tests should not even know that it exists. Your code should only ever set the password attribute with a cleartext.
These are integration and not controller tests. Controller tests are subclasses of ActionController::TestCase used in legacy applications. They should not be used. While this might seem like nickpicking you're only confusing yourself and others by conflating them.
Avoid monkeypatching a bunch of junk into ActiveSupport::TestCase or even worse Minitest::Test. If you MUST monkeypatch then write your methods in a module and include it so that the stack trace points there. But do it at the correct level in the class heirarchy. For example separate between the helpers for integration and system tests and include them into ActionDispatch::IntegrationTest and ActionDispatch::SystemTest instead of polluting all your tests.
If you need to add a lot of additional behavior don't reopen the class. Instead create your own test class which inherits from ActionDispatch::IntegrationTest and have your tests subclass it.
If you have code that you want to reuse in multiple tests like the test setup use modules that can be included where they are actually needed.
test_access doesn't belong in the shared subclass way up the tree. This belongs in an actual integration test which covers your authentication system flows or that a specific resource is authorized correctly.
Devise is an authentication system (who is the user?) - but what you're doing here is authorization (who can do what?). Devise doesn't provide any kind of authorization besides preventing access for users that are not authenticated. Neither should it (see SRP).
The index_manager method isn't a good way to implement authorization. If you want to reinvent the wheel make sure you raise an exception and use rescue_from so that the callback chain is halted and so that you're not repeating yourself. You may want to look into existing systems like CanCanCan and Pundit.
What you actually want here is something more like:
class AuthorizationError < StandardError
end
class ApplicationController
before_action :authenticate_user! # use an opt-out secure by default setup.
rescue_from AuthorizationError, with: :deny_access
private
def authorize_manager!
unless current_user.manager?
raise AuthorizationError.new("You need to be a manager to access this resource")
end
end
def deny_access(exception)
redirect_to root_path, error: exception.message
end
end
module AuthorizationIntegrationHelpers
def assert_denies_access
follow_redirect!
assert_current_path root_path
# ...
end
end
class FoosFlowTest < ActionDispatch::IntegrationTest
include AuthorizationIntegrationHelpers
include Warden::Test::Helpers
test "should not be accessable to non-managers" do
login_as(users(:one))
get '/foos'
assert_denies_access
end
end

Rails 6 Devise test helpers without RSpec

Devise test helpers with Rails 6 without Rspec doesn't seem to work. Here is the code, any idea why it might be getting errors?
Controller:
class VehiclesController < ApplicationController
before_action :authenticate_user!, only: [:new]
def index
#vehicles = Vehicle.all
end
def new
#vehicle = Vehicle.new
end
end
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative "../config/environment"
require "rails/test_help"
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include Devise::Test::IntegrationHelpers
end
user fixture:
valid_user:
first_name: "Toseef"
last_name: "zafar"
email: "exampleuser#gmail.com"
encrypted_password: <%= Devise::Encryptor.digest(User, '12345678') %>
controller test:
require "test_helper"
class VehiclesControllerTest < ActionDispatch::IntegrationTest
test "should be able to get to new form page" do
sign_in users(:valid_user)
get new_vehicles_url
assert_response :success
end
end
and this is the error I get:
Failure:
VehiclesControllerTest#test_should_be_able_to_get_to_new_form_page [/test/controllers/vehicles_controller_test.rb:12]:
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/users/sign_in>
Response body: <html><body>You are being redirected.</body></html>
rails test test/controllers/vehicles_controller_test.rb:9
Also, I don't know why it would point to http://www.example.com
The devise user model has got confirmable hence when we do sign_in users(:valid_user) devise creates a user but because the user is not confirmed (i.e. no confirmation email link clicking is involved) when we go to a secured URL it takes us back to login because user hasn't confirmed through clicking on the link from the email.
The solution to this is to set confirmed_at value to Time.now before sign_in
e.g.
#user = users(:valid_user)
#user.confirmed_at = Time.now
sign_in #user
after doing that the tests passed! :)

Can not test mailer action called in rspec rails

In mailer of rails, as I know all method will be class method.
But I can not test my mailer method called:
user_mailer_spec.rb:
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
notify_email = double(:send_notifition)
expect(UsersMailer.new).to receive(:notify_email).with(#user)
#user.save
end
user_mailer.rb:
def notify(user)
mail to: user.email, subject: "Example"
end
user.rb:
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self)
end
The above codes will not pass but when I change notifition to self.notifition, it pass:
def self.notify(user)
mail to: user.email, subject: "Example"
end
First of all, I'd like to point you to an awesome gem for testing emails: https://github.com/email-spec/email-spec.
I think the problem is that you're asserting on UsersMailer.new, thus putting a mock on a different instance than the one then being instantiated by the User model. I generally test emails like this without any issues:
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
mail = double(:mail)
expect(UsersMailer).to receive(:notify_email).with(#user).and_return(mail)
expect(mail).to receive(:deliver_later) # or deliver_now, if you don't use a background queue
#user.save
end
Note how I'm doing expect(UsersMailer) instead of expect(UsersMailer.new) and also take not that I'm asserting that the email is actually delivered (I think a deliver statement is missing in your code).
Hope that helps.
Solved:
Thank you #Clemens Kofler for supporting.
I have many mistaking in my code:
First: No need to install gem "email_spec", and change user.rb file
from
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self)
end
to
after_commit :send_notifition
private
def send_notifition
UsersMailer.notify(self).deliver
end
Second: Change user_mailer_spec.rb file
from
it "should call send_notifition method" do
#user = FactoryGirl.build(:user)
expect(#user).to receive(:send_notifition)
notify_email = double(:send_notifition)
expect(UsersMailer.new).to receive(:notify_email).with(#user)
#user.save
end
to
it "should call send_notifition_mail_if_created_new_hospital method" do
#user = FactoryGirl.build(:user)
# I don't know why "expect(#user).to receive(:send_notifition)" not passed here
mail = double(:mail)
expect(UsersMailer).to receive(:notify_email).with(#user).and_return(mail)
allow(mail).to receive(:deliver)
#user.save
end
Finally: config mailer in config/environments/test.rb for test environment can use mailer (because spec run in test env)

How do I log out a user in a Rails integration/controller unit test?

I have the following test to ensure sign in is required. Most of the tests require a signed in user, so I have the user sign in on setup. However for this test, I need them signed out.
require 'test_helper'
class DealsControllerTest < ActionDispatch::IntegrationTest
include Warden::Test::Helpers
setup do
#deal = deals(:one)
#user = users(:one)
# https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
#user.confirmed_at = Time.now
#user.save
login_as(#user, :scope => :user)
end
teardown do
Warden.test_reset!
end
test "require sign in for deal list" do
logout #user
get deals_url
assert_redirected_to new_user_session_path
end
I get the error
Failure:
DealsControllerTest#test_require_sign_in_for_deal_list [C:/Users/Chloe/workspace/fortuneempire/test/controllers/deals_controller_test.rb:35]:
Expected response to be a <3XX: redirect>, but was a <200: OK>
It says it will work, but it's just not working.
https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
If for some reason you need to log out a logged in test user, you can use Warden's logout helper.
logout(:user)
Rails 5.0.2
Devise 4.2.1
logout by itself without any parameters worked. Maybe logout :user would work too. I wasn't using FactoryGirl so was confusing it for a FactoryGirl symbol.

Using rspec, device and sentient_user

I am using Devise with sentient_user Gem - https://github.com/bokmann/sentient_user.
It works fine when I user the current_user in the model.
The Problem is that my Rspec test are failing .
In My Rspec test I have
describe MyModelWhereIUseCurrentUser do
include Devise::TestHelpers
before(:each) do
#user = Factory(:user)
sign_in #user
end
describe "current_user should work here" do
it "should do something" do
# Reference to MyModelWhereIUseCurrentUser and current_user is null there
end
end
end
Then in the describe block I called the model where I use
current user.
My Test Fails because current_user is nil.
Did I not sign in the user correctly or the problem is in the sentient_user ?
sentient_user adds a make_current method, which can be called on User instance. Your problem can be fixed by adding #user.make_current to before block.

Resources