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 "[email protected]"
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!