I have my controller which I'm trying to test.
class ShortLinksController < ApplicationController
after_action :increment_view_count, only: :redirect_to_original_url
def redirect_to_original_url
link = ShortLink.find(params[:short_url])
redirect_to "http://#{link.original_url}"
end
private
def increment_view_count
ShortLink.increment_counter(:view_count, params[:short_url])
end
end
This is the route for redirect_to_original_url:
get 's/:short_url', to: 'short_links#redirect_to_original_url', as: 'redirect_to_original_url'
And my Rspec tests:
describe "#redirect_to_original_url" do
let(:short_link) {ShortLink.create(original_url: 'www.google.com')}
subject {get :redirect_to_original_url, params: {short_url: short_link.id}}
it 'should increment the count by 1 original url is visited' do
expect {subject}.to change{ short_link.view_count }.by(1)
end
end
For some reason I get the following error when I run my tests:
expected `short_link.view_count` to have changed by 1, but was changed by 0
My logic works as I can see it increments that individual link view_count by 1, but not my test.
Check the default value of view_count when you create a object for ShortLink model using,
let(:short_link) {ShortLink.create(original_url: 'www.google.com')}
//Creating object
it 'should have value 0 when shortlink object is created' do
expect(short_link.view_count).to eq(0)
end
If this example fails then create object with default value for view_count as,
let(:short_link) {ShortLink.create(original_url: 'www.gmail.com',view_count: 0)}
Meanwhile as Jake Worth said your rspec test is not calling,
after_action :increment_view_count, only: :redirect_to_original_url
in your controller(check this by calling increment_view_count function from your redirect_to_original_url function and run your tests).
Since you created short_link variable you need to reload it to check that value was changed. Unless reloading it stores previous value.
expect { subject }.to change{ short_link.reload.view_count }.by(1)
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
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.
I am looking through some tests written by other people and i keep seeing ':where' in their tests. I guess its a stub, but just finding my feet with testing and want to know is it any different from a normal stubs, what does the naming imply?
describe "delete destroy" do
context "is not allowed by user" do
before do
allow(model).to receive(:where).and_return(no_instances)
allow(no_instances).to receive(:first).and_return(no_instances)
end
context "where the record is destroyed" do
before do
allow(instance).to receive(:destroy).and_return(true)
delete :destroy, params_id
end
sets_flash(:notice)
redirects_to('/airbrake_accounts')
end
context "where the record is not destroyed" do
before do
allow(instance).to receive(:destroy).and_return(false)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/airbrake_accounts')
end
end
context "where the record is not found" do
before do
allow(model).to receive(:where).and_return(no_instances)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/airbrake_accounts')
end
end
I can see what is going on here (I think), things like ':new' are controller actions right?
describe "photo create" do
before do
allow(model).to receive(:new).and_return(instance)
end
context "where all is not well" do
before do
allow(instance).to receive(:save).and_return(false)
post :create, params_new_instance
end
sets_flash(:error)
it "should render the new form" do
expect(response).to render_template("entries/new")
end
end
context "where all is well" do
before do
allow(instance).to receive(:save).and_return(true)
post :create, params_new_photo
end
sets_flash(:notice)
redirects_to ('/photos')
end
end
They are class or instance methods on the model. Lets just say that the model variable in your example is set to the Dog model and this is testing the DogController.
# model
class Dog
def where(params)
do_stuff
end
end
# controller
class DogController > ApplicationController
def destroy
#dogs = Dog.where(id: 1)
redirect :new
end
end
Now I want to test whats going on in my controller, but I don't want to test anything that my model actually does. I'm isolating a unit of my code to be tested. This is different than testing how it all works integrated together (google unit testing or integration testing).
In order to test just whats going on in my controller I stub the methods happening on my model to keep things isolated and clean. The way I do this is by stubbing it out in my controller spec. So In my DogControllerSpec I do:
before do
allow(Dog).to receive(:where).and_return([])
end
So I'm saying allow my dog class to receive the 'where' method call, but don't execute its logic, and instead return me an empty array.
Then I can setup up the code I'm actually testing which is that my destroy method renders new when called.
it "should render the new form" do
expect(response).to render_template("dogs/new")
end
The key here is really that you are attempting to decouple your tests, so that when you unit test your DogController#destroy you are not testing your Dog.where method. The reasoning being that if you change code in your Dog model it should not break specs in your DogControllerSpec.
They are using symbol #to_proc, I believe, to 'stub' calling the method #where on model. So that means the line:
allow(model).to receive(:where).and_return(no_instances)
is essentially
model.where #=> [].
:new is a controller action, but the way this code is using it is more like SomeClass.new, i.e. the method to create an instance of a class(which is the model).
I have the following controller:
def create
#order = Order.create(order_params)
OrderProcessorWorker.perform_async(#order.id)
end
And the following worker:
def perform(order_id)
#.. some logic
#order.status = 'processed'
#order.save
#.. some other logic
end
My question is... What is the correct way of testing that the job is changing the status of the Order? This gem https://github.com/philostler/rspec-sidekiq helps with testing that the job enqueued, delayed, retried...etc. But how do you test what is the job is actually doing?
EDIT: Here is what I have done:
describe OrderProcessorWorker do
describe '#perform' do
context 'an order without any pages' do
it 'changes the status of the order to missing_pages' do
FactoryGirl.create(:order_processing_status)
p OrderProcessingStatus.all # => This shows the just created order_processing_status
order = FactoryGirl.create(:order)
subject.perform(order.id)
end
end
end
end
What happens is that, the perform method that is being called is not the one defined. I have tried doing p 'hello worlds' and it does not show in there.
To test that your worker is doing the work you intend it to do - test perform rather than perform_async:
describe OrderProcessorWorker do
it 'changes order status' do
subject.perform(order_id)
expect(Order.find(id: order_id).status).to eq 'processed'
end
end
If you are testing create:
assert_equal 0, OrderProcessorWorker.jobs.size
post :create, ...
assert_equal 1, OrderProcessorWorker.jobs.size
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)