Search code examples
ruby-on-railsrspeccontrollerfunctional-testing

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 "[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.


Solution

  • 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!