I'm trying to run the following spec:
describe UsersController, "GET friends" do
it "should call current_user.friends" do
user = mock_model(User)
user.should_receive(:friends)
UsersController.stub!(:current_user).and_return(user)
get :friends
end
end
My controller looks like this
def friends
#friends = current_user.friends
respond_to do |format|
format.html
end
end
The problem is that I cannot stub the current_user method, as when I run the test, I get:
Spec::Mocks::MockExpectationError in 'UsersController GET friends should call current
_user.friends'
Mock "User_1001" expected :friends with (any args) once, but received it 0 times[0m
./spec/controllers/users_controller_spec.rb:44:
current_user is a method from Restful-authentication, which is included in this controller. How am I supposed to test this controller?
Thanks in advance
You will need to mock a user and then pass it into the login_as method to simulate logging in the user.
#user_session = login_as(stub_model(User))
UserSession.stubs(:new).returns(#user_session)
http://bparanj.blogspot.com/2006_12_01_archive.html
Related
I am testing my controller to ensure that a library class is called and that the functionality works as expected. NB: This might have been asked somewhere else but I need help with my specific problem. I would also love pointers on how best to test for this.
To better explain my problem I will provide context through code.
I have a class in my /Lib folder that does an emission of events(don't mind if you don't understand what that means). The class looks something like this:
class ChangeEmitter < Emitter
def initialize(user, role, ...)
#role = role
#user = user
...
end
def emit(type)
case type
when CREATE
payload = "some payload"
when UPDATE
payload = "some payload"
...
end
send_event(payload, current_user, ...)
end
end
Here is how I am using it in my controller:
class UsersController < ApplicationController
def create
#user = User.new(user_params[:user])
if #user.save
render :json => {:success => true, ...}
else
render :json => {:success => false, ...}
end
ChangeEmitter.new(#user, #user.role, ...).emit(ENUMS::CREATE)
end
end
Sorry if some code doesn't make sense, I am trying to explain the problem without exposing too much code.
Here is what I have tried for my tests:
describe UsersController do
before { set_up_authentication }
describe 'POST #create' do
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
post :create, user: user_params
expect(response.status).to eq(200)
// Here is the test for the emitter
expect(ChangeEmitter).to receive(:new)
end
end
end
I expect the ChangeEmitter class to receive new since it is called immediately the create action is executed.
Instead, here is the error I get:
(ChangeEmitter (class)).new(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
What am I missing in the above code and why is the class not receiving new. Is there a better way to test the above functionality? Note that this is Rspec. Your help will be much appreciated. Thanks.
You need to put your expect(ChangeEmitter).to receive(:new) code above the post request. When you are expecting a class to receive a method your "expect" statement goes before the call to the controller. It is expecting something to happen in the future. So your test should look something like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
expect(ChangeEmitter).to receive(:new)
post :create, user: user_params
expect(response.status).to eq(200)
end
EDIT
After noticing that you chain the "emit" action after your call to "new" I realized I needed to update my answer for your specific use case. You need to return an object (I usually return a spy or a double) that emit can be called on. For more information on the difference between spies and doubles check out:
https://www.ombulabs.com/blog/rspec/ruby/spy-vs-double-vs-instance-double.html
Basically a spy will accept any method called on it and return itself whereas with a double you have to specify what methods it can accept and what is returned. For your case I think a spy works.
So you want to do this like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
emitter = spy(ChangeEmitter)
expect(ChangeEmitter).to receive(:new).and_return(emitter)
post :create, user: user_params
expect(response.status).to eq(200)
end
Right now I have a subscriber controller that creates a subscriber but that is not what I want to test. I also have a method in the controller that add 1 to the visit attribute on the Subscriber(I'll post the code) that is the method I want to test but I'm not sure how? I'm new to rails and Rspec so I'm having trouble grasping the concepts. I'll post my test and controller for clarity.
CONTROLLER:
def search
#subscriber = Subscriber.new
end
def visit
#subscriber = Subscriber.find_by_phone_number(params[:phone_number])
if #subscriber
#subscriber.visit =+ 1
#subscriber.save
flash[:notice] = "thanks"
redirect_to subscribers_search_path(:subscriber)
else
render "search"
end
end
TEST
it "adds 1 to the visit attribute" do
sign_in(user)
subscriber = FactoryGirl.create(:subscriber)
visits_before = subscriber.visit
post :create, phone_number: subscriber.phone_number
subscriber.reload
expect(subscriber.visit).to eq(visits_before)
end
ERROR MESSAGE:
As you can see that is the method I want to test. The current test in place does not work but I thought it might help to show what I'm thinking. Hopefully this is enough info, let me know if you want to see anything else?
I think you could do something like this:
it 'adds 1 to the visit attribute' do
# I'm assuming you need this, and you are creating the user before
sign_in(user)
# I'm assuming your factory is correct
subscriber = FactoryGirl.create(:subscriber)
visits_before = subscriber.visit
post :create, subscriber: { phone_number: subscriber.phone_number }
subscriber.reload
expect(subscriber.visit).to eq(visits_before)
end
Since you are checking subscriber.visits you should change Subscriber to subscriber:
expect { post :create, :subscriber => subscriber }.to change(subscriber, :visit).by(1)
visits is a method of an instance, not a class method.
I think you're testing the wrong method. You've already stated that your create action works, so no need to test it here. Unit tests are all about isolating the method under test.
Your test as it is written is testing that post :create does something. If you want to test that your visit method does something, you'd need to do something like this:
describe "#GET visit" do
before { allow(Subscriber).to receive(:find).and_return(subscriber) }
let(:subscriber) { FactoryGirl.create(:subscriber) }
it "adds one to the visit attribute" do
sign_in(user)
expect { get :visit }.to change(subscriber, :visit).by(1)
end
end
I have a controller create action that creates a new blog post, and runs an additional method if the post saves successfully.
I have a separate factory girl file with the params for the post I want to make. FactoryGirl.create calls the ruby create method, not the create action in my controller.
How can I call the create action from the controller in my RSpec? And how would I send it the params in my factory girl factories.rb file?
posts_controller.rb
def create
#post = Post.new(params[:post])
if #post.save
#post.my_special_method
redirect_to root_path
else
redirect_to new_path
end
end
spec/requests/post_pages_spec.rb
it "should successfully run my special method" do
#post = FactoryGirl.create(:post)
#post.user.different_models.count.should == 1
end
post.rb
def my_special_method
user = self.user
special_post = Post.where("group_id IN (?) AND user_id IN (?)", 1, user.id)
if special_post.count == 10
DifferentModel.create(user_id: user.id, foo_id: foobar.id)
end
end
end
Request specs are integration tests, using something like Capybara to visit pages as a user might and perform actions. You wouldn't test a create action from a request spec at all. You'd visit the new item path, fill in the form, hit the Submit button, and then confirm that an object was created. Take a look at the Railscast on request specs for a great example.
If you want to test the create action, use a controller spec. Incorporating FactoryGirl, that would look like this:
it "creates a post" do
post_attributes = FactoryGirl.attributes_for(:post)
post :create, post: post_attributes
response.should redirect_to(root_path)
Post.last.some_attribute.should == post_attributes[:some_attribute]
# more lines like above, or just remove `:id` from
# `Post.last.attributes` and compare the hashes.
end
it "displays new on create failure" do
post :create, post: { some_attribute: "some value that doesn't save" }
response.should redirect_to(new_post_path)
flash[:error].should include("some error message")
end
These are the only tests you really need related to creation. In your specific example, I'd add a third test (again, controller test) to ensure that the appropriate DifferentModel record is created.
In my system, I have a user that have one company that have multiple accounts.
User sign in system using Devise, and have a virtual attribute called selected_company that was setted in CompaniesController.
I want to make multiple tests in AccountsController with this scenario.
I have this code to sign_in user, this code works well:
before :each do
#user = create(:user)
#user.confirm!
sign_in #user
end
But I must to have a specific context that I tried to code as:
context 'when user already selected a company' do
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
#user.selected_company = #company
end
it "GET #index must assings #accounts with selected_company.accounts" do
get :index
expect(assigns(accounts)).to match_array [#account]
end
end
But this code won't work, when I run it I got this error:
undefined method `accounts' for nil:NilClass
My AccountsController#index have only this code:
def index
#accounts = current_user.selected_company.accounts
end
I'm new in rspec and TDD and I have some time to test everything I want, and I want to test everything to practice rspec.
I don't know if this is the best way to test this things, so I'm open to suggestions.
Replace with:
expect(assigns(:accounts)).to match_array [#accounts]
Note, :accounts instead of just account.
Also, as I see it, you don't have #accounts in your spec. Please declare that, too. :)
Probably you are not saving selected_company and when you call this on your controller it returns nil.
Try save #user.save after set selected_company:
context 'when user already selected a company' do
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
#user.selected_company = #company
#user.save
end
it "GET #index must assings #accounts with selected_company.accounts" do
get :index
expect(assigns(accounts)).to match_array [#account]
end
end
Hope to help you.
Finaly, I found the problem!
I changed the before statement to:
before :each do
#company = create(:company)
#account = create(:account)
#company.accounts << #account
controller.current_user.selected_company = #company
end
And changed assigns(accounts) to assings(:accounts) (with symbol) in expect method.
I would like to test my controller after I added strong_parameters gem, how to do that?
I tried:
Controller
class EventsController < ApplicationController
def update
#event = Event.find(params[:id])
respond_to do |format|
if #event.update_attributes(event_params)
format.html { redirect_to(#event, :notice => 'Saved!') }
else
format.html { render :action => "new" }
end
end
end
private
def event_params
params.require(:event).permit!
end
end
Specs
describe EventsController do
describe "PUT update" do
describe "with forbidden params" do
let(:event) { Event.create! title: "old_title", location: "old_location", starts_at: Date.today }
it "does not update the forbidden params" do
put :update,
id: event.to_param,
event: { 'title' => 'new_title', 'location' => 'NY' }
assigns(:event).title.should eq('new_title') # explicitly permitted
assigns(:event).location.should eq("old_location") # implicitly forbidden
response.should redirect_to event
end
end
end
end
Errors
1) EventsController PUT update with forbidden params does not update the forbidden params
Failure/Error: assigns(:event).title.should eq('new_title') # explicitly permitted
NoMethodError:
undefined method `title' for nil:NilClass
# ./spec/controllers/events_controller_spec.rb:13:in
I see a few things going on here.
The fact that it says undefined method on line 13 is because the #event variable is not being assigned, so assigns(:event) is returning nil.
You should check out why that is happening, maybe you have some authentication that is preventing you from updating the record? Maybe you can check out the testing logs to see what is actually going on.
It could be because you are using let() which is lazy and the record is not actually available yet when you try to search for it, but I'm not completely sure. You could try using let!() and see if that helps.
With regards to the actual usage of strong parameters, if you only want title to be assignable you need to do something like the following:
params.require(:event).permit(:title)
If you use permit!, the event parameters hash and every subhash is whitelisted.