【问题标题】:Rspec controller test - should_receive on instance of model returns 0 timesRspec 控制器测试 - 模型实例上的 should_receive 返回 0 次
【发布时间】:2014-02-18 23:43:57
【问题描述】:

我在测试控制器时遇到了 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)>'

无法理解为什么该更改会阻止规范通过。我确实在规范中的“帖子”之后附加了一个调试器,并查看了闪存是否正确(即,确保控制器中的正确代码树正在运行)并且是正确的。

【问题讨论】:

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


    【解决方案1】:

    问题是你需要说should_receive而不是should receive。这是因为any_instanceUser.any_instance.should receive 意味着无论any_instance 返回的对象(RSpec::Mocks::AnyInstance::Recorder)都应该接收到调用。当然,这不是您想要的,因为该对象与控制器实例化的对象不同。 (实际上它甚至不是User。)所以Recorder 有一个特殊的should_receive 方法,可以满足您的实际需求。棘手!

    【讨论】:

      【解决方案2】:

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

      此外,需要在您期望的呼叫之前设置期望(例如,在您的情况下,在post 之前,如@PaulAJungwirth 的评论中所述。

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

      expect_any_instance_of(User).to receive(:send_token)
      

      用您陈述的期望线解决问题。

      【讨论】:

      • 这很合理 - 但是测试此功能的方法是什么?
      • 您可以模拟User.find_by_email 方法,如果您愿意,可以检查参数,然后返回您的实例。或者您可以对User 的任何实例设置期望值。这是几个选项。
      • 使用 expect_any_instance_of(User).to receive(:send_token) 给我:Failure/Error: Unable to find matching line from backtrace Exactly one instance should have received the following message(s) but didn't: send_token
      • 抱歉,我什至没有注意到您一开始就使用了expect_any_instance_of。我的回答显然不适用。我会在您阅读本文后的几分钟内删除它,如果/当我想出一些东西时,我会编辑/取消删除。
      • 我认为User.any_instance.should receive 需要 post 之前出现,对吗?您必须在运行代码之前设置存根方法。
      猜你喜欢
      • 2018-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-04
      相关资源
      最近更新 更多