سؤال

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top