How do you use callback collections generated by FactoryGirl? - ruby-on-rails

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.

Related

This code can be rails unit test with rspec?

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.

RSpec test for ActiveAdmin member_action

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

Rspec for sessions controller

I am trying to write a rspec for sessions controller. But i am not able to do so.
I wanted to test controller for valid and invalid valid attributes. Tests should pass if valid attributes are passed for a particular user and should fail if invalid attributes are passed for a given user.
require 'rails_helper'
RSpec.describe SessionsController, type: :controller do
let(:user) { User.create(name: "some",email: 'other#example.com', password: 'rous',password_confirmation: "some") }
describe "get login path" do
it "login page" do
get :new
expect(response).to render_template('sessions/new')
end
end
describe "valid attributes" do
it "create a session" do
post :create, session: { email: user.email, password: user.password }
expect(response.status).to render_template('/users/#user.id')
end
end
describe "invalid attributes" do
it "does not create a session" do
post :create, session: { email: user.email, password: "12345" }
expect(response.status).to render_template('sessions/new')
end
end
describe "does not create a session" do
it "if email is empty" do
post :create, session: {password: "some"}
expect(response.status).to render_template('sessions/new')
end
it "if password is empty" do
post :create, session: {email: "some#gmail.com"}
expect(response.status).to render_template('sessions/new')
end
end
end
Describe "invalid attributes" and "valid attributes" are not working fine.
If email/password are wrong, user redirected to session#new page.
If email & password are correct, user should to redirected to user_path i.e., users show page.
You should really use a request or feature spec instead here as they drive the full stack. You not only want to test that your controller redirects somewhere - you really want to test that redirects somewhere and signs a user in. Preferably without poking inside the internals or mocking the actual authentication process that you're supposed to be testing in the first place.
Both the RSpec and Rails teams disencourage writing controller tests/specs for new applications. Test the behaviour of your application. Not how it does its job.
require "rails_helper"
RSpec.feature "User sign in", type: :feature do
let!(:user) { FactoryBot.create(:user) }
scenario "User signs in with valid crentials" do
visit "/sessions/new"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign In"
expect(page).to have_text "You have been signed in"
end
scenario "User signs in with invalid crentials" do
visit "/sessions/new"
fill_in "Email", with: user.email
fill_in "Password", with: "nottherightpassword"
click_button "Sign In"
expect(page).to_not have_text "You have been signed in"
expect(page).to have_text "Invalid email or password"
end
end
Or as a lower level request spec:
require "rails_helper"
RSpec.describe "Sessions", type: :request do
let!(:user) { FactoryBot.create(:user) }
describe "POST /sessions" do
context "with valid credentials" do
it "redirects the user" do
post '/sessions',
params: { session: { email: user.email, password: user.password } }
expect(response).to redirect_to user_path(user)
follow_redirect!
expect(response.body).to include("You have been signed in")
end
end
context "with invalid credentials" do
it "does not redirect the user" do
post '/sessions',
params: { session: { email: user.email, password: "nottherightpassword" } }
expect(response).to_not redirect_to user_path(user)
expect(response.body).to include("Invalid email or password")
end
end
end
end

Creating multiple FactoryGirl records. Validation failed: Email has already been taken

I'm writing the RSpec tests for the index action for Project. A project belongs_to a client, which belongs_to a user.
What I'm trying to do is create 30 instances of project to be used in pagination. All belonging to the same user.
When I run the tests, I'm giving the following error (I've tried rake db test:prepare):
Failure/Error: before(:all) { #projects = FactoryGirl.create_list(:project, 30, user: user) }
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
factories.rb
FactoryGirl.define do
factory :user do
name "John Nana"
email "john#nana.com"
password "woopwoop1"
password_confirmation "woopwoop1"
end
factory :client do
name "Widget inc."
user
end
factory :user1 do
name "Bob Inc"
email "lol#catz.com"
password "nayy1"
password_confirmation "nayy1"
end
factory :project do
sequence(:name) { |n| "Project #{n}" }
fee "250"
client
end
end
project_pages_spec.rb
require 'spec_helper'
describe "create" do
let(:user) { FactoryGirl.create(:user) }
let(:submit) { "Create project" }
describe "when signed in" do
before do
capybara_sign_in(user)
#client = user.clients.create(name: "Widget inc.")
visit new_project_path
end
describe "with valid information" do
describe "from /projects" do
before do
visit projects_path
click_link "Add project"
fill_in "Project name", with: "Brochure"
fill_in "Fee", with: "1000"
select(#client.name, from: 'project_client_id')
end
it "should create a project and redirect to /projects" do
expect { click_button submit }.to change(Project, :count).by(1)
current_path.should == '/projects'
end
end
before do
fill_in "Project name", with: "Brochure"
fill_in "Fee", with: "1000"
select(#client.name, from: 'project_client_id')
end
it "should create a project" do
expect { click_button submit }.to change(Project, :count).by(1)
end
end
describe "with invalid information" do
before do
too_long_project = "a" * 50
fill_in "Project name", with: too_long_project
fill_in "Fee", with: "1000000000"
select(#client.name, from: 'project_client_id')
end
it "should not create a project" do
expect { click_button submit }.not_to change(Project, :count)
end
end
end
describe "when not signed in" do
before { visit new_project_path(user) }
it "should redirect to signin path" do
current_path.should == '/signin'
end
end
end
describe "index" do
let(:user) { FactoryGirl.create(:user) }
before do
capybara_sign_in(user)
visit projects_path
end
describe "pagination" do
before(:all) { #projects = FactoryGirl.create_list(:project, 30, user: user) }
after(:all) { Project.delete_all }
it { should have_selector('div.pagination') }
it "should list each user" do
Project.paginate(page: 1).each do |project|
expect(page).to have_selector('li', text: project.name)
end
end
it "should list all projects" do
#projects.each do |p|
expect(page).to have_selector('li', p.name)
end
end
end
end
You can use FactoryGirl's Sequences to generate unique email addresses:
factory :user do
name "John Nana"
sequence(:email) { |n| "person#{n}#example.com" }
password "woopwoop1"
password_confirmation "woopwoop1"
end

How to test after_sign_in_path_for(resource)?

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

Resources