This is my controller:
class PlansController
def use_new_card
#user = User.find_by_id(new_card_params[:user_id])
if new_stripe_card
...
end
private
def new_card_params
params.permit(:user_id, :stripeToken)
end
def new_stripe_card
StripeService.new({user_id: #user.id, customer_id: #user.stripe_customer_id, source: new_card_params[:stripeToken]}).new_card
end
end
I'm trying to write a controller spec that tests that when the proper parameters are POST to the use_new_card method, then new_stripe_card's StripeService.new gets these parameters appropriately.
My spec looks like this so far:
describe "when proper params are passed" do
before do
#user = User.create(...)
end
it "should allow StripeService.new to receive proper parameters" do
StripeService.should_receive(:new).with({user_id: #user.id, customer_id: #user.stripe_customer_id, source: "test"})
post :use_new_card, user_id: #user.id, stripeToken: "test"
end
end
But with that I'm getting
Failure/Error: post :use_new_card, user_id: #user.id, stripeToken: "test"
NoMethodError:
undefined method `new_card' for nil:NilClass
Totally fair, but I'm not sure how to fix this... I can't just stub new_card because a stubbed method on a nil object will still throw this error (I tried adding a StripeService.any_instance.stub(:new_card).and_return(true) already into the before block)
Stubbed methods return nil by default. Use and_return to specify the value returned by the stub::
StripeService.should_receive(:new).and_return(whatever)
or using the newer syntax
expect(StripeService).to receive(:new).and_return(whatever)
EDIT
Pardon my hand-waving. Your stub must return an object that will act like an instance of StripeService to the extent required for the purposes of the test. So for example:
let(:new_card) { double }
let(:new_stripe_service) { double(new_card: new_card) }
...
expect(StripeService).to receive(:new).and_return(new_stripe_service)
Now when the test refers to new_stripe_service, RSpec will return a test double that has a method stub named #new_card, which itself is a double. You can add whatever additional stubs you need to make the test pass. Alternatively, you can return an actual instance of StripeService for new_stripe_service.
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
I'm currently working on a team that requires 100% code coverage and I cannot for the life of me get hit this single line method to get my current coverage up to 100%.
I have a base controller that looks like this which multiple other controller extend from.
module Owners
module Pets
class BaseController < Owners::ApplicationController
private
def current_pet
current_owner.all_pets.find(params[:pet_id])
end
end
end
end
My spec for this controller looks like this.
require 'rails_helper'
Rails.describe Owners::Pets::BaseController, type: :controller do
routes { Owners::Engine.routes }
controller Owners::Pets::BaseController do
def index
current_pet
end
end
let(:user) { double :user, id: 1, owner: nil }
before { sign_in(user) }
before do
allow(request.env['warden']).to receive(:user).and_return user
allow(Owners::User).to receive(:find).with(user.id).and_return user
end
context 'with current owner and pet' do
let(:owner) { create :owner }
let(:pet) { create(:owner_pet, owner: owner) }
describe '#current_pet' do
before do
allow(controller).to receive(:current_pet).and_return pet
routes.append { get 'index' => 'owners/pets/base#index' }
end
it do
get :index
is_expected.to eq pet
end
end
end
end
The spec is failing with the error "No route matches {:action=>"index", :controller=>"owners/pets/base"}" Routes.append should add the proper route, correct?
Update: By moving my routes { Owners::Engine.routes } line above the anonymous controller it no longer throws an error related to routes. However now it is comparing pet with the actual controller class. The output is too long to paste here but it's essentially:
expected: #<Owners::Pet>
got: #<#<Class:0x007fc65c860518>:0x007fc65f83a248>
with a whole bunch of attributes and methods.
This test has no value. You're stubbing the very method that you're testing. Even if the method body of #current_pet raised an exception, the test would still pass.
Generally, it's best to avoid testing private methods directly. You should be able to test this method indirectly via one of the classes which inherits from Owners::Pets::BaseController.
When you use the syntax it { is_expected.to ... } Rspec must infer what "it" is based on the test itself. The subject method can be used to explicitly specify what "it" is; in cases where subject is not present, Rspec will instantiate a new instance of the class which is being tested. In your case that would be the controller itself.
Try setting the subject explicitly inside the context of the #current_pet block.
For example,
context 'with current owner and pet' do
let(:owner) { create :owner }
let(:pet) { create(:owner_pet, owner: owner) }
describe '#current_pet' do
before do
allow(controller).to receive(:current_pet).and_return pet
routes.append { get 'index' => 'owners/pets/base#index' }
end
# set this to whatever you want "is_expected.to" to be
subject { controller.current_pet }
it do
get :index
is_expected.to eq pet
end
end
end
Obligatory Note: I have to agree with other posters that this test is not very useful. Conventional wisdom is to only test public methods (private methods get tested by their usage within a public method).
I'm testing to make sure that a created user is assigned to my instance variable #user. I understand what get means, but I'm not sure what to write for the test. I'm returning with an argument error for a bad URI or URL. What's wrong with my test and how do I fix it?
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :users
# I'm confused on what this line above means/does. What does the hash :users refer
#to
assigns[:user].should == [p]
end
The expected URI object or string error refers to get :users and the error is as follows
Failure/Error get :users
ArgumentError:
bad argument: (expected URI object or URI string)
I guess that what you want is
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :show, id: p.id
assigns(:user).should == p
end
The line you were not sure about checks that content of the assigned variable (#user) in the show view of the user p, is equal to the p user you just created more information there
what action are you trying to test? usually, for creation, you need to test that the controller's "create" action creates a user and assigns an #user variable
I would test it this way:
describe 'POST create' do
it 'creates a user' do
params = {:user => {:name => 'xxx', :lastname => 'yyy'}}
User.should_receive(:create).with(params)
post :create
end
it 'assigns the user to an #user instance variable' do
user = mock(:user)
User.stub!(:create => user)
post :create
assigns(:user).should == user
end
end
notice that I stub/mock all user methods, since you are testing a controller you don't have to really create the user, you only test that the controller calls the desired method, the user creation is tested inside the User model spec
also, I made 2 tests (you should test only 1 thing on each it block if possible, first it test that the controller creates a user, then I test that the controller assigns the variable
I'm assuming your controller is something like this:
controller...
def create
#user = User.create(params[:user])
end
which is TOO simple, I guess you have more code and you should test that code too (validations, redirects, flash messages, etc)
I am trying to create a non ActiveRecord model in my Ruby on Rails application according to http://railscasts.com/episodes/121-non-active-record-model. I am facing hard time testing it though.
I have following class in my app/models/sms.rb file.
class Sms
def initialize
# Do stuff here
end
def deliver
# Do some stuff here
end
end
I am unable to mock deliver method on my Sms class.
it 'should do someting' do
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
post :create, :user_id => #user.id
flash[:notice].should == "SMS sent successfully."
response.should redirect_to(some_url)
end
In my controller code I do have a line that says #sms.deliver. Yet above gives following error:
Failure/Error: #sms.should_receive(:deliver).and_return(true)
(Mock Sms).deliver(any args)
expected: 1 time
received: 0 times
Any pointers?
Variables beginning with # are instance variables. The #sms your controller refers to is not the same #sms as your spec has defined.
Try changing
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
to
Sms.any_instance.should_receive(:deliver).and_return(true)
If your version of RSpec doesn't have the any_instance method, you'll need to stub :new on the class:
#sms = mock(Sms)
Sms.stub(:new).and_return(#sms)
#sms.should_receive(:deliver).and_return(true)
My create user method in the users_controller.rb looks like:
def process_login
is_login_valid(params[:user][:user_name], params[:user][:password])
if logged_in?
redirect_to root_url
else
#user = User.new(params[:user][:user_name]
redirect_to :action => 'login'
end
What I have currently:
describe UsersController do
describe "Post 'process_login'"
it "should be successful" do
post 'process_login'
response.should be_success
end
end
end
The methods is_login_valid and logged_in? are all included in the application_controller, and are methods from a ruby class I have in my /lib folder called LoginSystem.rb
My test is failing since it isn't mocking things correctly, this is my first time doing this so hoping someone can help me out.
Error message:
UsersController POST 'process_login' should be successful
Failure/Error: post 'process_login'
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
# ./app/controllers/users_controller.rb:11:in `process_login'
# ./spec/controllers/users_controller_spec.rb:21
Aah, Thanks for the error message. I'm assuming that line 11 is is_login_valid(params[:user][:user_name], params[:user][:password]).
As you're not sending any params in your test post params[:user] is nil hence the nil.[] error (when the controller is looking for params[:user][:user_name]), you set params by passing them as a hash as the 2nd parameter to post in your test.
I think you actually need
controller.stub(:logged_in?) { true }
Or if you want to test that the logged_in method is actually being called
controller.should_receive(:logged_in?).and_return(true)
The 2nd version will cause the test to fail unless the method logged_in? is called once and only once
You may also need the controller.stub(:is_login_valid} { true } as suggested by #jaydel if you're getting an error message about this method being missing as well
I believe:
controller.stub(:is_login_valid} { true }
should get you where you want to go, if I understand correctly.