Rspec控制器测试 - 模型实例上的should_receive返回0次

时间:2014-02-18 23:43:57

标签: ruby-on-rails rspec controller functional-testing

在测试控制器时,我遇到了Rails 4.0.3和rspec 2.14.1的问题。

控制器的相关部分是:

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是:

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

规范是:

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

而且,我的用户工厂:

FactoryGirl.define do
  factory :user do
    name "MyString"
    email "a@b.com"
  end
end

当我将规范的@u = create行更改为@u = create(:user)时(即省略primary_phone),我得到:

 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)>'

这是预期的。当我将其更改为包含primary_phone时,我得到:

  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)>'

无法理解为何此更改会阻止规范传递。我确实在规范中的'post'之后附加了一个调试器并查看了flash是否正确(即,确保控制器中正确的代码树正在运行)并且它是。

2 个答案:

答案 0 :(得分:4)

问题是你需要说should_receive而不是should receive。这是因为any_instanceUser.any_instance.should receive表示无论对象any_instance返回(RSpec::Mocks::AnyInstance::Recorder)都应该接收呼叫。当然这不是你想要的,因为该对象与控制器实例化的实例不同。 (实际上它甚至不是User。)因此Recorder有一个特殊的should_receive方法可以完成您真正想要的操作。棘手!

答案 1 :(得分:2)

您在规范中创建的User对象与User方法创建并发送sign_in的{​​{1}}对象不同,因此您设置的期望值您错误消息中反映的send_token上的内容将无法满足。它们都与相同的底层数据库记录相关联,但它们是不同的Ruby对象。 (注意:在您的问题的第一个版本中,您为规范显示的代码与您显示的错误不匹配,因为代码显示在@u上设置了期望,而您的错误消息反映了对{的预期{1}}

此外,需要将之前的设置为您预期的电话(例如,在您的情况下User.any_instance之前,如@PaulAJungwirth的评论中所述。

最后,作为@PaulAJungwirth提供的答案的替代方案,您可以使用:

@u

用你所说的期望线解决问题。