I've got custom member_action in my Active Admin panel which is responsible for resending devise reset password instructions.
admin/users.rb
ActiveAdmin.register User do
member_action :reset_password do
user = User.find(params[:id])
user.send_reset_password_instructions
redirect_to(admin_user_path(user),
notice: "Password reset email sent to #{user.email}")
end
end
How to write RSpec tests for such an action? The only thing I found is this one and I think it's not quite related to my problem.
I was trying to sth like below:
require 'rails_helper'
describe Admin::UsersController, type: :controller do
include Devise::TestHelpers
let!(:admin) { create(:admin_user) }
before(:each) do
sign_in admin
end
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before(:each) do
User.should_receive(:find).at_least(:once).and_return(user)
get :show
end
it 'sends email' do
get :reset_password
expect(user).should_receive(:send_reset_password_instructions)
end
end
end
But I'm getting an error:
ActionController::UrlGenerationError:
No route matches {:action=>"reset_password", :controller=>"admin/users"}
Personally I prefer to use a feature test, since when using active admin, UI stuff handle by the framework:
RSpec.feature 'Reset Password', type: :feature do
let(:user) { create :user }
before do
login_as(user, scope: :user)
end
scenario 'can delete future episode' do
visit some_path
click_link 'Reset Password'
expect(page.current_path).to eq(admin_user_path(user))
expect(page).to have_content("Password reset email sent to #{user.email}")
end
end
Ok, it turns out small adjustments (pass the user.id in params) make the trick.
describe Admin::UsersController, type: :controller do
include Devise::Test::ControllerHelpers
before { sign_in admin }
let!(:admin) { create(:admin_user) }
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before do
allow(User).to receive(:find).at_least(:once) { user }
get :show, params: { id: user.id }
end
it 'sends email' do
get :reset_password, params: { id: user.id }
expect(flash[:notice]).to match("Password reset email sent to #{user.email}")
end
end
end
Related
I'm studying rails and rspec.
And I made rspec unit test (request test) on rails application.
But after searching on google, I'm wonder if my job is on right way.
Can my code be a "Unit test by function(not a method, web site's feature ex)create, show, delete..) of rails application" ?
this is my code with request test.
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:users) { create_list(:user, 10) }
let(:user_id) { users.first.id }
let(:user) { create(:user) }
def send_request_to_store_user(name, mailaddress)
post '/users', params: {
user: {
name: users.first.name,
mailaddress: users.first.mailaddress
}
}
end
def http_status_success_and_body_element_check(body_element)
expect(response).to have_http_status(:success)
expect(response.body).to include(body_element)
end
describe 'GET' do
context 'Get /users test' do
it 'test user list page' do
get '/users'
http_status_success_and_body_element_check('User List')
end
end
context 'Get /users/create test' do
it 'test user create page' do
get '/users/create'
http_status_success_and_body_element_check('create user')
end
end
context 'Get /users/:id/edit' do
it 'test user edit page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('edit user')
end
end
context 'Get /users/:id' do
it 'test user show page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('show user')
end
end
end
describe 'POST' do
context 'test store new user' do
it 'test create new user' do
send_request_to_store_user(user.name, user.mailaddress)
expect do
create(:user)
end.to change { User.count }.from(User.count).to(User.count + 1)
end
it 'test redirect after create' do
send_request_to_store_user(user.name, user.mailaddress)
expect(response).to have_http_status(302)
end
end
end
describe 'DELETE' do
it 'test delete user' do
expect do
delete "/users/#{user_id}"
end.to change { User.count }.from(User.count).to(User.count - 1)
expect(response).to have_http_status(302)
end
end
describe 'PUT' do
context 'user update' do
it 'test user information update' do
old_name = users.first.name
new_name = 'new_name'
expect do
put "/users/#{user_id}", params: {
user: {
name: new_name
}
}
end.to change { users.first.reload.name }.from(old_name).to(new_name)
expect(response).to have_http_status(:redirect)
end
end
end
end
this is my code with test on model
require 'rails_helper'
RSpec.describe User, type: :model do
it 'user must have name and mailaddress' do
user = create(:user)
expect(user).to be_valid
expect(user.name).not_to be_nil
expect(user.mailaddress).not_to be_nil
end
it 'mailaddress must include #' do
# user = FactoryBot.create(:user)
# If rails_helper.rb has config.include FactoryBot::Syntax::Methods,
# Can use shortcut. Don't have to FactoryBot.create
user = create(:user)
# Test pass if email match with regexp
expect(user.mailaddress).to match(/\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/)
end
end
I don't think these tests are valuable (meaningful).
Here's my reasoning:
What are these tests telling you? That the Rails router is working? That the controller is responding with the right action? Neither of these are your responsibility to test. Rails has that covered.
If you want to know "does the index page render?" and "can I CRUD a user?" then write system tests with Capybara that simulate the whole flow. That way you are testing the real-world interaction with your whole system.
I have a custom AutorizationAdapter that I would like to test using RSpec:
class AdminAuthorization < ActiveAdmin::AuthorizationAdapter
def authorized?(_action, _subject = nil)
user.admin?
end
end
Initially I used a custom method but since I'm using Devise, using a custom AuthorizationAdapter seemed to be the way to go.
How would you go about testing it ? I tought one way to test it is to create a request spec for one of the controller and test for status code & redirection, something like that:
require 'rails_helper'
RSpec.describe 'AdminUsers', type: :request do
describe 'GET /admin_users' do
context 'admin' do
let(:admin_user) { create(:admin_user) }
before { sign_in super_user }
get admin_users_path
expect(response).to have_http_status(200)
end
context 'non admin' do
let(:user) { create(:user) }
before { sign_in user }
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
context 'non logged in user' do
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
end
end
I'm not sure this is the way to go.
These look reasonable to me. You can also look at the unit and feature specs that are in the ActiveAdmin test suite. However, AuthorizationAdapter itself is a PORO so you should be able to unit test in isolation: in the example given above that would be a fairly trivial test.
So I want to test controller which is using devise.
require 'rails_helper'
describe ArticlesController do
before(:all) { #article = FactoryGirl.create(:article) }
sign_in_admin
describe 'GET new' do
let(:call_request) { get :new }
before { call_request }
context 'admin signed in' do
it { is_expected.to respond_with :ok }
end
context 'admin signed out' do
it { is_expected.to respond_with 302 }
end
end
end
I followed this guide from devise wiki. So I created ControllerMacros module as you can see here:
module ControllerMacros
def sign_in_admin
before(:each) do
#request.env['devise.mapping'] = Devise.mappings[:admin]
#admin = FactoryGirl.create(:admin)
sign_in #admin
end
end
end
And included it, and other needed things to rails_helper:
require_relative 'support/controller_macros.rb'
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
(...)
Here is admin factory:
FactoryGirl.define do
factory :admin do
email 'email#email.com'
password 'password'
end
end
But now I don't know how to sign out admin? As sign_in_admin is executing before each test, but I need to sign admin out for some of the tests. What is the best way to test this controller with signed in/out admin?
btw I'm kinda new to testing
Devise provides some helper methods for testing, including login_user which takes a user, so you shouldn't need to create the sign_in_admin function. Also by using before(:all) at the top of the block, you've signed in the admin for the entire suite of specs.
Use before(:each) within the individual contexts that you want a signed_in_admin.
describe ArticlesController do
describe 'GET new' do
let(:call_request) { get :new }
let(:admin_user) { FactoryGirl.create(:admin) }
before { call_request }
context 'admin signed in' do
sign_in(admin_user)
it { is_expected.to respond_with :ok }
end
context 'admin not signed in' do
it { is_expected.to respond_with 302 }
end
end
end
Better way is to close before action into a context. The contexts are signed_in, and non-singed in. So you can do something as follows:
describe 'GET new' do
let(:call_request) { get :new }
before { call_request }
context "signed in" do
before { sign_in_admin }
it { is_expected.to respond_with :ok }
end
context 'non-singed in' do
it { is_expected.to respond_with 302 }
end
end
I'm writing tests for my code using Rspec and FactoryGirl - where a User has_one Organization and an Organization has_many users. Organization name is set to unique, and I was testing the users happily using the association: organization (within my User factory) feature of FactoryGirl (with a bunch of hacked together traits for wrong_user tests. It got to the stage where this wasn't adequate (I need a collection of users within an Organization for some of the tests, and the above setup doesn't work properly) so I'm now trying to build collections of users inside my Organization Factories instead. This is working for some of the tests but not others, and I think I'm not using the created user(s) correctly in my tests.
Factory Code:
factory :organization do
organization_name "New Example Org"
before(:create) do |organization|
organization.users << FactoryGirl.build(:user)
organization.users << FactoryGirl.build(:user, email: "email2#example.com")
end
trait :wrong do
organization_name "Wrong Org"
before(:create) do |organization|
organization.users << FactoryGirl.build(:user, email: "email3#example.com")
end
end
end
Failing test code:
describe "as wrong user" do
let(:organization) { FactoryGirl.create(:organization, :wrong)}
let(:user) {organization.users.first}
let(:wrong_user) { organization.users.last }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#show action", type: :request do
before { get user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title(user.name)) }
specify { expect(response).to redirect_to(root_path) }
end
describe "submitting a GET request to the Organizations#show action", type: :request do
before { get organization_path(wrong_user.organization) }
specify { expect(response.body).not_to match(full_title(wrong_user.organization.organization_name)) }
specify { expect(response).to redirect_to(root_path) }
end
end
The results;
26) Authentication authorization as wrong user submitting a GET request to the Organizations#show action should redirect to "/"
Failure/Error: specify { expect(response).to redirect_to(root_path) }
Expected response to be a <redirect>, but was <200>
# ./spec/requests/authentication_pages_spec.rb:109:in `block (5 levels) in <top (required)>'
25) Authentication authorization as wrong user submitting a GET request to the Organizations#show action should not match "Title | Wrong Org"
Failure/Error: specify { expect(response.body).not_to match(full_title(wrong_user.organization.organization_name)) }
What am I doing wrong here?
I managed to fix this in the end. The issue was that I can only call one factory "Organization", and therefore any subsequent traits or builds I put on that factory are all associated with the same Organization_id. The fix was as follows:
Factory Code:
factory :organization do
trait :name_one do
organization_name "New Example Org"
end
trait :name_two do
organization_name "Any Wrong Org"
end
trait :with_users do
before(:create) do |organization|
organization.users << FactoryGirl.build(:user)
organization.users << FactoryGirl.build(:user, name: "Wrong Name", email: "email2#example.com")
end
end
trait :wrong do
before(:create) do |organization|
organization.users << FactoryGirl.build(:user, name: "another wrong name", email: "email3#example.com")
end
end
end
factory :user do
name "Example Name"
email "email#example.com"
password "foobar"
password_confirmation "foobar"
trait :as_admin do
admin true
end
end
end
Test Code:
describe "as wrong user" do
let(:organization) { FactoryGirl.create(:organization, :name_one, :with_users)}
let(:user) {organization.users.first}
let(:wrong_org) { FactoryGirl.create(:organization, :name_two, :wrong)}
let(:wrong_user) {wrong_org.users.last}
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action", type: :request do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_path) }
end
describe "submitting a PATCH request to the Users#update action", type: :request do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_path) }
end
describe "submitting a GET request to the Users#show action", type: :request do
before { get user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title(user.name)) }
specify { expect(response).to redirect_to(root_path) }
end
describe "submitting a GET request to the Organizations#show action", type: :request do
before { get organization_path(wrong_user.organization) }
specify { expect(response.body).not_to match(full_title(wrong_user.organization.organization_name)) }
specify { expect(response).to redirect_to(root_path) }
end
end
describe "as non-admin user" do
let(:organization) { FactoryGirl.create(:organization, :name_one, :with_users)}
let(:non_admin) {organization.users.first}
let(:user) {organization.users.last}
before { sign_in non_admin, no_capybara: true }
describe "submitting a DELETE request to the Users#destroy action", type: :request do
before { delete user_path(user) }
specify { expect(response).to redirect_to(root_path) }
end
end
end
All the tests are now green - so by placing the Organization Name into specific traits (rather than as a general part of the Factory) I'm able to create two different organizations and add users to each. Hope it helps someone else.
I have devise authentication and registration set up on my Rails app. I'm using after_sign_in_path_for() to customise the redirect when the user signs in based on various scenarios.
What I'm asking is how to test this method? It seems hard to isolate since it is called automatically by Devise when the user signes in. I want to do something like this:
describe ApplicationController do
describe "after_sign_in_path_for" do
before :each do
#user = Factory :user
#listing = Factory :listing
sign_in #user
end
describe "with listing_id on the session" do
before :each do
session[:listing_id] = #listing.id
end
describe "and a user in one team" do
it "should save the listing from the session" do
expect {
ApplicationController.new.after_sign_in_path_for(#user)
}.to change(ListingStore, :count).by(1)
end
it "should return the path to the users team page" do
ApplicationController.new.after_sign_in_path_for(#user).should eq team_path(#user.team)
end
end
end
end
end
but that's obviously not the way to do it because I just get an error:
Failure/Error: ApplicationController.new.after_sign_in_path_for(#user)
RuntimeError:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<ApplicationController:0x00000104581c68 #_routes=nil, #_action_has_layout=true, #_view_context_class=nil, #_headers={"Content-Type"=>"text/html"}, #_status=200, #_request=nil, #_response=nil>
So, how can I test this method?
Oddly, I was wondering this very thing today. Here's what I came up with. I created an anonymous subclass of ApplicationController. In this anonymous subclass, I exposed the protected methods that I wanted to test as public methods. Then I tested them directly.
describe ApplicationController do
controller do
def after_sign_in_path_for(resource)
super resource
end
end
before (:each) do
#user = FactoryGirl.create(:user)
end
describe "After sigin-in" do
it "redirects to the /jobs page" do
controller.after_sign_in_path_for(#user).should == jobs_path
end
end
end
On a similar note - if you want to test the redirect after sign-up, you have two options.
First, you can follow a pattern similar to above and very directly test the method in RegistrationsController:
require 'spec_helper'
describe RegistrationsController do
controller(RegistrationsController) do
def after_sign_up_path_for(resource)
super resource
end
end
describe "After sign-up" do
it "redirects to the /organizations/new page" do
#user = FactoryGirl.build(:user)
controller.after_sign_up_path_for(#user).should == new_organization_path
end
end
end
Or, you can take a more integration-testing sort of approach and do the following:
require 'spec_helper'
describe RegistrationsController do
describe "After successfully completing the sign-up form" do
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
it "redirects to the new organization page" do
post :create, :user => {"name" => "Test User", "email" => "test#example.com", "password" => "please"}
response.should redirect_to(new_organization_path)
end
end
end
For the newcomers, I would recommend doing this way:
RSpec.describe ApplicationController, type: :controller do
let(:user) { create :user }
describe "After sing-in" do
it "redirects to the /yourpath/ home page" do
expect(subject.after_sign_in_path_for(user)).to eq(yourpath_root_path)
end
end
end
I found this answer through Google recently and thought I would add my solution. I didn't like the accepted answer because it was testing the return value of a method on the application controller vs testing the desired behavior of the app.
I ended up just testing the call to create a new sessions as a request spec.
RSpec.describe "Sessions", type: :request do
it "redirects to the internal home page" do
user = FactoryBot.create(:user, password: 'password 123', password_confirmation: 'password 123')
post user_session_path, params: {user: {email: user.email, password: 'password 123'}}
expect(response).to redirect_to(internal_home_index_path)
end
end
(Rails 5, Devise 4, RSpec 3)
context "without previous page" do
before do
Factory.create(:user, email: "junior#example.com", password: "123456", password_confirmation: "123456")
request.env["devise.mapping"] = Devise.mappings[:user]
post :create, user: { email: "junior#example.com", password: "123456" }
end
end
it { response.should redirect_to(root_path) }
context "with previous page" do
before do
Factory.create(:user, email: "junior#example.com", password: "123456", password_confirmation: "123456")
request.env["devise.mapping"] = Devise.mappings[:user]
request.env['HTTP_REFERER'] = 'http://test.com/restaurants'
post :create, user: { email: "junior#example.com", password: "123456" }
end
it { response.should redirect_to("http://test.com/restaurants") }
end