I am testing the controllers with RSpec, FactoryGirls.
It is my factories.rb
FactoryGirl.define do
factory :user do |user|
user.sequence(:name) { Faker::Internet.user_name }
user.email Faker::Internet.email
user.password "password"
user.password_confirmation "password"
end
factory :article do
user
title Faker::Lorem.sentence(5)
content Faker::Lorem.paragraph(20)
end
end
How can i create an article of the user here
And this is articles_controller_spec
describe ArticlesController do
let(:user) do
user = FactoryGirl.create(:user)
user.confirm!
user
end
describe "GET #index" do
it "populates an array of articles of the user" do
#how can i create an article of the user here
sign_in user
get :index
assigns(:articles).should eq([article])
end
it "renders the :index view" do
get :index
response.should render_template :index
end
end
end
The older version, instead of traits, is this:
describe ArticlesController do
..
describe "GET #index" do
it "populates an array of articles of the user" do
article = FactoryGirl.create(:article, :user => user)
sign_in user
get :index
assigns(:articles).should eq([article])
end
..
end
describe ArticlesController do
let(:user) do
user = FactoryGirl.create(:user)
user.confirm!
user
end
describe "GET #index" do
it "populates an array of articles of the user" do
#how can i create an article of the user here
sign_in user
get :index
assigns(:articles).should eq([article])
end
it "renders the :index view" do
get :index
response.should render_template :index
end
it "assign all atricles to #atricles" do
get :index
assigns(:atricles).your_awesome_test_check # assigns(:articles) would give you access to instance variable
end
end
end
you can specify an User factory with articles already
FactoryGirl.define do
factory :user do |user|
user.sequence(:name) { Faker::Internet.user_name }
user.email Faker::Internet.email
user.password "password"
user.password_confirmation "password"
end
factory :article do
user
title Faker::Lorem.sentence(5)
content Faker::Lorem.paragraph(20)
end
trait :with_articles do
after :create do |user|
FactoryGirl.create_list :article, 2, :user => user
end
end
end
then in your controller test
FactoryGirl.create :user, :with_articles # => returns user with 2 articles
UPDATE
i think you want to see all articles per user.. if thats the case use
get :index, {:id => user.id}
that way you look for the user and get all articles in your controller
#user = User.find(params[:id]);
#articles = #user.articles
if thats not the case then just doing
#articles = Article.all
after using the trait :with_articles should display at least 2 Articles
you can test this with a simply asserting like
expect(#article.size).to eq(2)
Related
I've started to use Rspec and right now I wrote several successfully worked and pretty difficult tests. But as I need more practice I did refactor of these tests few times.
I not found an answer for my question in Rspec's documentation and that's why I here.
The question is about directive let that provides an ability to return some objects by first call and not only, you know.
My current part of rspec code is:
RSpec.describe 'Users', type: :request do
describe 'profiles' do
context 'should be visible by' do
it 'related company managers' do
company = create(:company)
sign_in create(:manager, :company, company: company) # Pay attention on this
get user_path(create(:member, :company, company: company)) # this
expect(response).to have_http_status 200
end
it 'related company owners' do
company = create(:company)
sign_in create(:owner, :company, company: company) # this
get user_path(create(:member, :company, company: company)) # and this
expect(response).to have_http_status 200
end
end
end
end
There are only two example of 63 that are under the User's spec, but they are enough. I want to refactor the code to use let which will define a method with parameters, like that:
RSpec.describe 'Users', type: :request do
let(:member) do |entity_name = :company, entity = create(entity_name)|
create :member, entity_name, entity_name => entity
end
let(:manager) do |entity_name = :company, entity = create(entity_name)|
create :manager, entity_name, entity_name => entity
end
let(:owner) do |entity_name = :company, entity = create(entity_name)|
create :owner, entity_name, entity_name => entity
end
describe 'profiles' do
context 'should be visible by' do
it 'related company managers' do
company = create(:company)
sign_in manager(:company, company) # Become more readable here
get user_path(member(:company, company)) # here
expect(response).to have_http_status 200
end
it 'related company owners' do
company = create(:company)
sign_in owner(:company, company) # here
get user_path(member(:company, company)) # and here.
expect(response).to have_http_status 200
end
end
end
end
Right after the refactor the Guard says:
1) Users profiles should be visible by related company managers
Failure/Error: sign_in manager(:company, company) # Become more readable here
ArgumentError:
wrong number of arguments (given 2, expected 0)
# ./spec/requests/users_spec.rb:38:in `block (4 levels) in <top (required)>'
From the memorized_helpers.rb of Rspec core I saw:
def let(name, &block)
# We have to pass the block directly to `define_method` to
# allow it to use method constructs like `super` and `return`.
raise "#let or #subject called without a block" if block.nil?
raise(
"#let or #subject called with a reserved name #initialize"
) if :initialize == name
MemoizedHelpers.module_for(self).__send__(:define_method, name, &block)
# Apply the memoization. The method has been defined in an ancestor
# module so we can use `super` here to get the value.
if block.arity == 1
define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
else
define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
end
end
It looks like let's blocks should be defined with parameters. Or not?
I haven't enough experience to determine it and I would be glad to find it out.
I've found possible solution: return a lambda and call it in place
RSpec.describe 'Users', type: :request do
let(:member) do
->(entity_name = :company, entity = create(entity_name)) do
create :member, entity_name, entity_name => entity
end
end
let(:manager) do
->(entity_name = :company, entity = create(entity_name)) do
create :manager, entity_name, entity_name => entity
end
end
let(:owner) do
->(entity_name = :company, entity = create(entity_name)) do
create :owner, entity_name, entity_name => entity
end
end
describe 'profiles' do
context 'should be visible by' do
it 'related company managers' do
company = create(:company)
sign_in manager.(:company, company) # Pay attention on the dot here
get user_path(member.(:company, company)) # here
expect(response).to have_http_status 200
end
it 'related company owners' do
company = create(:company)
sign_in owner.(:company, company) # here
get user_path(member.(:company, company)) # and here.
expect(response).to have_http_status 200
end
end
end
end
This is not exactly that I want, but seems like.
I think you want something like:
RSpec.describe 'Users', type: :request do
describe 'profiles' do
let(:request) { get user_path(user) }
let(:user) { create(:member, :company, company: company) }
let(:company) { create(:company) }
shared_examples_for 'has visibility to user' do
before { sign_in employee }
it do
request
expect(response).to have_http_status(:success)
end
end
context 'manager' do
let(:employee) { create(:manager, company: company) }
it_behaves_like 'has visibility to user'
end
context 'owner' do
let(:employee) { create(:owner, company: company) }
it_behaves_like 'has visibility to user'
end
end
end
Similarly, you should be able to do something like:
it_behaves_like 'has visibility to user' do
let(:employee) { create(:manager, company: company) }
end
with shared examples. I suggest checking out http://www.betterspecs.org/.
(Aside, it's confusing to have a factory named :member and not :user since the GET request is for user_path; I recommend renaming it.)
The another solution is creation of helpers. For example, create a file in spec/support/users_helper.rb.
module UsersSpecHelper
def member(entity = :company)
create :member, *entity(entity)
end
def manager(entity = :company)
create :manager, *entity(entity)
end
def owner(entity = :company)
create :owner, *entity(entity)
end
private
def entity(entity)
if entity.is_a? Symbol
[entity, entity => create(entity)]
else
name = entity.class.to_s.downcase.to_sym
[name, name => entity]
end
end
end
RSpec.configure do |config|
config.include UsersSpecHelper, type: :request
end
Uncomment in spec/rails_helper.rb the line:
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
and then it can be used even better:
RSpec.describe 'Users', type: :request do
describe 'profiles' do
context 'should be visible by' do
it 'managers of related company' do
company = create(:company)
sign_in manager(company) # Changes are here
get user_path(member(company)) # here
expect(response).to have_http_status 200
end
it 'owners of related company' do
company = create(:company)
sign_in owner(company) # here
get user_path(member(company)) # and here.
expect(response).to have_http_status 200
end
end
end
end
Factory user.rb
FactoryGirl.define do
factory :user do
username "Matin"
password "123456"
factory :admin do
admin true
end
end
end
user_controller_spec.rb
before :each do
session[:user_id] = create(:admin).id
end
describe "user access to orders" do
describe "GET#index" do
it " populates an array of all users" do
smith = create(:user,username:'smith')
jones = create(:user,username:'jones')
get :index
expect(assigns(:users)).to match_array([smith, jones])
end
it "render the :index template" do
get :index
expect(response).to render_template :index
end
end
This can not pass test and show error
expected collection contained:[User id: 2, username: "smith"] actual
collection contained: [User id: 1, username: "Matin"] the extra
elements were: [User id: 1, username: "Matin"]
I think error happens because I did not put admin user into match_array. How to add admin user into match_array?
You can save admin user to variable and then use it:
before :each do
#admin = create(:admin)
session[:user_id] = #admin.id
end
...
get :index
expect(assigns(:users)).to match_array([#admin, smith, jones])
I have the following route:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks",
:registrations => 'users/registrations',
:sessions => "users/sessions" }
and the following controller test (registrations_controller_spec.rb):
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => #user, :user => #attr
puts #user.errors.messages
#user.display_name.should == #attr[:display_name]
end
end
end
end
Now, when I run rspec spec I get (what I think) are weird results:
The "should create a user" test passes. The user count has increased by 1.
However, my "should change the user's display name" fails as follows:
1) Users::RegistrationsController PUT 'update' Success should change the user's display name
Failure/Error: #user.display_name.should == #attr[:display_name]
expected: "Test"
got: "Boyd" (using ==)
And the weird bit is that my statement:
puts #user.errors.messages
Renders the following message:
{:email=>["was already confirmed, please try signing in"]}
What's going on? The user is signed in! That's proved by the fact that the Rspec error returned the display_name of "Boyd". And why is it displaying a message looks as though it's related with account confirmation as opposed to updating a user's details?
Any help would be greatly appreciated!
This works. Thanks holtkampw for seeing what I wasn't! I put some extra code in there just to double check and everything is well!
it "should change the user's display name" do
subject.current_user.should_not be_nil
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
puts "Old display name: " + subject.current_user.display_name
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
puts "New display name: " + subject.current_user.display_name
end
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
I was wondering if i could have some feedbacks with the controller spec bellow. In fact i'm new when writing specs and controller's spec are way different from model's spec ! So i'm wondering if i may not go in the wrong direction...
subjects_controller.rb
def show
#subject = Subject.find(params[:id])
if #subject.trusted?(current_user)
#messages = #subject.messages
else
#messages = #subject.messages.public
#messages = #messages + #subject.messages.where(:user_ids => current_user.id)
#messages.uniq!
end
# sort the list
#messages = #messages.sort_by(&:created_at).reverse
if !#subject.company.id == current_user.company.id
redirect_to(subjects_path, :notice => "Invalid subject")
end
end
subjects_controller_spec.rb
require 'spec_helper'
describe SubjectsController do
before(:each) do
#subject = mock_model(Subject)
end
context "for signed users" do
before(:each) do
#current_user = sign_in Factory(:user)
end
context "GET #show" do
before(:each) do
Subject.stub!(:find, #subject).and_return(#subject)
end
context "when current_user is trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(true)
#subject.should_receive(:messages).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when current_user is not trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(false)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:public).and_return(messages)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:where).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when subject's company is not equal to current_user's company" do
# I have no idea of how to implement ==
end
end
end
end
Factories.rb
Factory.define :user do |u|
u.first_name 'Test User' #
u.username 'Test User' #
u.surname 'TheTest' #
u.email 'foo#foobar.com' #
u.password 'please' #
u.confirmed_at Time.now #
end
As far as I can tell you're on the right path. The basic idea is to completely isolate your controller code from model and view in these tests. You appear to be doing that--stubbing and mocking model interaction.
Don't write RSpec controller specs at all. Use Cucumber stories instead. Much easier, and you get better coverage.