Expect mock to receive message in rails controller - ruby-on-rails

I'm having trouble setting expectations for a mock used in one of my controllers:
controllers/blark_controller.rb
class BlarkController < ApplicationController
def show
user = User.first
user.inspect
render nothing: true
end
end
spec/controllers/blark_controller_spec.rb
require 'spec_helper'
describe BlarkController do
describe 'GET :show' do
let(:user) { mock_model User }
before do
User.stub(:first).and_return(user)
get :show
end
it 'blarks' do
expect(user).to receive(:inspect)
end
end
end
Results in this:
22:04:58 - INFO - Running: spec/controllers/blark_controller_spec.rb
BlarkController
GET :show
blarks (FAILED - 1)
Failures:
1) BlarkController GET :show blarks
Failure/Error: expect(user).to receive(:inspect)
(Double "User_1001").inspect(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/controllers/blark_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
Finished in 0.15579 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/blark_controller_spec.rb:13 # BlarkController GET :show blarks
Can I set expectations on mocks in specs?

You can, but the way you're doing it is wrong.
You're calling an action (get :show) and then after calling it, setting a future expectation (expect(user).to receive(:inspect)). Obvious this won't work because you've already called the action, there is no future for this test.
You either need to set the expectation before calling the action (switch the order of the statements) or use rspec's recently-added spies feature to set expectations after the fact. This uses have_received rather than receive.
For more detail: https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/spies/spy-on-a-stubbed-method-on-a-partial-mock

Related

How do we print response body in rspec

I am trying to write an rspec file for my meetings_controller.rb so as to check if the values returned from my database are correct.
When I go to localhost:3000/meeting.json, this is the result of my data from the database
my rspec file is trying to check if the correct values are returned.
I created a folder called controller under specs (after I have installed rspec)and have the file meeting_controller_spec.rb
require 'rails_helper'
# Change this ArticlesController to your project
RSpec.describe MeetingsController, type: :controller do
describe "GET #index" do
# check index
it "returns a success response" do
get :index
puts response.body
expect(response).to have_http_status(:ok)
end
end
end
I tried to print the response body but nothing is returning. Is there a way to do this?
(base) adam-a01:reservation adrianlee$ rspec
.
Finished in 0.0892 seconds (files took 12.15 seconds to load)
1 example, 0 failures
Btw this is my meeting_controller.rb
class MeetingsController < ApplicationController
before_action :set_meeting, only: [:show, :edit, :update, :destroy]
# GET /meetings
# GET /meetings.json
def index
#meetings = Meeting.all
#meeting = Meeting.new
end
end
Update: I also tried this method as suggested below but it didnt work still
# Change this ArticlesController to your project
RSpec.describe MeetingsController, type: :controller do
describe "GET #index" do
# check index
it "returns a success response" do
get :index
raise response.body
expect(response).to have_http_status(:ok)
end
end
end
This is the error raised
Failures:
1) MeetingsController GET #index returns a success response
Failure/Error: raise response.body
RuntimeError:
# ./spec/controllers/meeting_controller_spec.rb:10:in `block (3 levels) in <top (required)>'
If you are trying to get this value for a debugging purpose you should use byebug or pry gems.
If you choose byebug, add it to your gemfile gem 'byebug' on test environment.
run bundle install and after that you are able to use it on your tests
So replace the puts with byebug
describe "GET #index" do
# check index
it "returns a success response" do
get :index
byebug
expect(response).to have_http_status(:ok)
end
end
At the console now you are exactly there. Just type response.body and enter to print it's value.
When you are done, just type c and then enter to release the console e continue your tests.
Also check here for more information about debugging with byebug
In my case I do it like this:
require 'rails_helper'
describe SuppliersController, type: :controller do
describe 'GET /suppliers' do
let!(:access_token) { create :access_token }
let!(:admin_user_token) do
create :user_token, resource_owner: create(:admin_user)
end
context 'when the requester is an admin' do
it 'returns HTTP status 200 (OK)' do
allow(controller).to receive(:doorkeeper_token) { access_token }
#request.env['HTTP_X_USERTOKEN'] = admin_user_token.token
get :index
raise response.body.inspect ## Here is what prints the value in the console.
body = JSON.parse response.body
expect(response).to have_http_status :ok
expect(body['status']).to eq 'OK'
end
end
end
end
To run the test:
rspec spec/controllers/suppliers/suppliers_controller_index_spec.rb
And it gives me the output:
F
Failures:
1) SuppliersController GET /suppliers when the requester is an admin returns HTTP status 200 (OK)
Failure/Error: raise response.body.inspect
RuntimeError:
"{\"status\":\"OK\",\"message\":\"Your request has been processed successfully.\",\"data\":[],\"page\":{\"current_page\":1,\"prev_page\":null,\"next_page\":null,\"per_page\":25,\"total_pages\":0}}"
# ./spec/controllers/suppliers/suppliers_controller_index_spec.rb:17:in `block (4 levels) in <top (required)>'
Finished in 0.47594 seconds (files took 2.82 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/suppliers/suppliers_controller_index_spec.rb:11 # SuppliersController GET /suppliers when the requester is an admin returns HTTP status 200 (OK)
I have to say raise a runtime error to check variable value is not a good practice,maybe you should try gem 'pry' or gem 'byebug' suggested by #DR7.
Another thing you need make sure is did you run rails s and rspec in different environment?This maybe lead to they connect to separate db.

Rspec controller test - should_receive on instance of model returns 0 times

I'm having an issue with Rails 4.0.3 and rspec 2.14.1 in testing a controller.
The relevant portion of the controller is:
class LoginsController < ApplicationController
def sign_in
#user = User.find_by(email: params[:email])
# ... - a few other codepaths but nothing that looks for primary_phone
if params[:email]
#user.send_token
flash[:notice] = "blah blah"
end
end
User.rb is:
class User < ActiveRecord::Base
# ...
def send_token
raise 'Tried to send token to contact with no phone number' if primary_phone.nil?
SMSSender.sms(primary_phone,"Your login code is: #{generate_token}")
end
end
The spec is:
require 'spec_helper'
describe LoginsController do
it "sends a token if a valid email is provided" do
#u = create(:user, primary_phone: "abc")
User.any_instance.should receive(:send_token)
post 'sign_in', email: #u.email
end
end
And, my user factory:
FactoryGirl.define do
factory :user do
name "MyString"
email "a#b.com"
end
end
When I change the spec's #u = create line to #u = create(:user) (ie, omitting the primary_phone), I get:
Failure/Error: post 'sign_in', email: #u.email
RuntimeError:
Tried to send token to contact with no phone number
# ./app/models/user.rb:16:in `send_token'
# ./app/controllers/logins_controller.rb:19:in `sign_in'
# ./spec/controllers/logins_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
This is as expected. When I change it back to include the primary_phone, I get:
1) LoginsController sign_in sends a token if a valid email is provided
Failure/Error: User.any_instance.should receive(:send_token)
(#<RSpec::Mocks::AnyInstance::Recorder:0x007ff537ed4bd8>).send_token(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/controllers/logins_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
Having trouble understanding why that change would prevent the spec from passing. I did attach a debugger right after the 'post' in the spec and looked at the flash to see if it was correct (i.e., to ensure the proper code tree in the controller was being run) and it is.
The problem is you need to say should_receive rather than should receive. This is because of the any_instance. User.any_instance.should receive means that whatever object any_instance returns (an RSpec::Mocks::AnyInstance::Recorder) should receive the call. Of course that's not what you want, because that object is also not the same instance as what the controller instantiates. (In fact it's not even a User.) So the Recorder has a special should_receive method that does what you actually want. Tricky!
The User object you've created in your spec is not the same User object that the sign_in method creates and sends send_token to, so the expectations you set on #u as reflected in your error message are not going to be met. They both are associated with the same underlying database record, but they are different Ruby objects. (Note: In the first version of your question, the code you showed for your spec didn't match the error you showed, as the code showed setting an expectation on User.any_instance whereas your error message reflected setting an expectation on #u
Further, the expectations need to be set prior to the call you are expecting (e.g. prior to the post in your case, as noted in the comment by #PaulAJungwirth.
Finally, as an alternative to the answer provided by #PaulAJungwirth, you can use:
expect_any_instance_of(User).to receive(:send_token)
to address the problem with the your stated expectation line.

No route matches using rspec on a get request

What am I forgetting?
routes:
get "/comingsoon" => "visitors#comingsoon"
resources :visitors
controller:
class VisitorsController < ApplicationController
def comingsoon
#new_visitor = Visitor.new
end
end
spec:
require 'spec_helper'
describe VisitorsController do
describe "GET /comingsoon" do
it "should be happy" do
get "/comingsoon"
response.should be_success
end
end
end
And here's the result:
✗ rspec spec/controllers/visitors_controller_spec.rb
F
Failures:
1) VisitorsController GET /comingsoon should be valid
Failure/Error: get "/comingsoon"
ActionController::RoutingError:
No route matches {:controller=>"visitors", :action=>"/comingsoon"}
# ./spec/controllers/visitors_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
Finished in 0.14226 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/visitors_controller_spec.rb:6 # VisitorsController GET /comingsoon should be valid
What am I forgetting?
In your spec file replace get "/comingsoon"
with get "comingsoon"
When you spec a controller with rspec the operand of the http verb (get, post, put, delete) is an action of the controller rather than a url.
Possibly daft suggestion, but you have a view right? Otherwise you have to tell your controller to render something.

RSpec + FactoryGirl should_receive failing

I can't figure out why this RSpec test fails. Any advice? I'm new-ish to FactoryGirl, RSpec, and TDD in general.
Controller:
def update
#vendor = current_user.vendors.find(params[:id])
if #vendor.update_attributes(params[:vendor])
redirect_to vendor_path(#vendor)
else
render 'edit'
end
end
Test:
require 'spec_helper'
describe VendorsController do
login_user
before :each do
#user = subject.current_user
#vendor = FactoryGirl.create(:vendor, :user => #user)
end
[...]
describe 'POST update' do
def do_update
post :update, :id => #vendor.id, :vendor => FactoryGirl.attributes_for(:vendor)
end
[...]
it 'should update a given vendor' do
do_update
#vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
end
end
end
Factory:
FactoryGirl.define do
factory :vendor do
name 'Widget Vendor'
user
end
end
The Failure:
Failures:
1) VendorsController POST update should update a given vendor
Failure/Error: #vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
(#<Vendor:0x007faeb75e77d0>).update_attributes({:name=>"Widget Vendor"})
expected: 1 time
received: 0 times
# ./spec/controllers/vendors_controller_spec.rb:108:in `block (3 levels) in <top (required)>'
Update:
I'm a little closer, now. I changed the test to the following:
it 'should update a given vendor' do
Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
do_update
end
And the new error is:
Failures:
1) VendorsController POST update should update a given vendor
Failure/Error: post :update, :id => #vendor.id, :vendor => FactoryGirl.attributes_for(:vendor)
#<Vendor:0x007ff30d765900> received :update_attributes with unexpected arguments
expected: ({:name=>"Widget Vendor"})
got: ({"name"=>"Widget Vendor"})
# ./app/controllers/vendors_controller.rb:33:in `update'
# ./spec/controllers/vendors_controller_spec.rb:98:in `do_update'
# ./spec/controllers/vendors_controller_spec.rb:108:in `block (3 levels) in <top (required)>'
Answer...?
Well, this worked. There has to be a better way of doing this, though:
Vendor.any_instance.should_receive(:update_attributes).with(JSON.parse(FactoryGirl.attributes_for(:vendor).to_json)).and_return(true)
I think you are doing it wrong.
The #vendor object in specs is another one that in your controller, so it doesn't receive "update_attributes" method.
You can try this (rspec 2.5+ probably):
Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
Or you can check if object attributes has changed:
expect{
do_update
}.to change(...)
I believe you need to set your expectations before posting the request; otherwise, by the time it hits your expectation the object has already been set. So move do_update after your should_receive line:
it 'should update a given vendor' do
#vendor.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor))
do_update
end
You can use the Hash stringify keys method in rails:
Vendor.any_instance.should_receive(:update_attributes).with(FactoryGirl.attributes_for(:vendor).stringify_keys)

Rspec problem mocking model in controller

I set up a controller which handles omniauth authentications which are worked into a custom built authentication system. i am trying to test the logic for how authentications are handled (ex: if user already has/does not have account, if user is/isn't currently logged in, etc.). as such i have a Authorization model and a authorizations controller. The action to create a authorization has this general outline:
class AuthorizationsController < ApplicationController
def create
omniauth = request.env['omniauth.auth']
authorization = Authorization.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authorization
# Authorization already established, log in user
elsif current_user
# User is logged in but wants to add another omniauth authentication
else
# Create user and associate them with omniauth authentication
end
end
end
I am trying to test this logic in Rspec but have been having issues. Heres is what I am working with in my spec:
describe AuthorizationsController do
render_views
describe "POST 'create'" do
describe "with an already existing authorization" do
it "should log the user in" do
#authmock = mock_model(Authorization)
Authorization.should_receive(:find_by_provider_and_uid).and_return(#authmock)
post :create, :provider => 'twitter'
current_user?(#authmock.user).should == true
response.should redirect_to(root_path)
end
end
end
end
I am under the impression that this should assign my mocked Authorization model (#authmock) to the local variable authorization in my controller when the assignment call is made, thus making 'if authorization' return true. However whenever I true to run this spec I get this error:
Failures:
1) AuthorizationsController POST 'create' with an already existing authorization should log the user in
Failure/Error: post :create, :provider => 'twitter'
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
# ./app/controllers/authorizations_controller.rb:5:in `create'
# ./spec/controllers/authorizations_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
Can anyone enlighten me as to what I am doing wrong here?
Edit:
since the question was raised as to whether or not the assignment of omniauth was causing issues, I commented out that line to see what would happen and got the following error:
1) AuthorizationsController POST 'create' with an already existing authorization should log the user in
Failure/Error: post :create, :provider => 'twitter'
NameError:
undefined local variable or method `omniauth' for #<AuthorizationsController:0xb41809c>
# ./app/controllers/authorizations_controller.rb:5:in `create'
# ./spec/controllers/authorizations_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
which tells me that the problem is with the mock or stub as the find_by_provider_and_uid function is still being evaluated and is not stubbed when the test runs
Are you specing
current_user?(#authmock.user).should == true
or
response.should redirect_to(root_path)
I think that first expectation should not be tested here, because you've mocked 'if authorization' block, so you should spec what happens then!

Resources