如何在控制器规范中模拟模型,实例和帮助程序?

时间:2014-02-03 09:06:19

标签: ruby-on-rails rspec

require 'spec_helper'

describe UsersController do
  let(:user){ double(User, id: 2, name: "Jimbo", email: 'jimbo@email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }

  before do
    mock_model("User")
  end

  describe 'PATCH #update' do
    User.should_receive(:find).with(user.id.to_s).and_return user      
    user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true

    patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }

    flash[:error].should == "could not update user"
    response.status.should == 200
  end
end

代码库:

def update
    @user = User.find(params[:id])

    if @user.update_attributes(user_params)
        redirect_to @user, flash: { success: 'succesfully updated user' }
    else
        flash.now[:error] = "could not update user"
        render 'edit'
    end
end

虽然上面的规范通过(并且只用了0.05秒!)我正确地做了吗?随着模拟高于请求,以及下面的“正常”预期呢?看起来有点笨拙。它不仅难以阅读,而且如果一个期望失败,所有这些都会失败。

是什么让我觉得我做错了是我得到的一个奇怪的错误。请参阅describe块的第二行,我说用户(user)的实例应该使用更新的属性触发update_attributes?请注意我已注释掉的and_return true方法。当它被链接时,运行上面的规范会让我感到厌烦:

  1) UsersController should_receive
     Failure/Error: patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
     NoMethodError:
       undefined method `model_name' for RSpec::Mocks::Mock:Class

虽然这是我的头脑,但我认为这是因为用户'实例'实际上不是一个实例,只是一个哈希。我想因为我使用了mock_model并且双重继承了那个模拟模型,所以所有的active-record-y东西都会被设置。

无论如何,我应该如何正确地编写这个规范? 我不想使用FactoryGirl,我想保留所有内容,以便我的规格快速准确地报告错误。

1 个答案:

答案 0 :(得分:1)

是的,在这种控制器测试的情况下,您的基本流程是“正常的”,除了您的最内层代码需要在it块内(并且可能并且只是没有正确转录)。

但是,您没有使用mock_model来电,因为您没有对结果做任何事情。此方法不会对您传递字符串名称的类进行任何根本性更改,它只是创建一个模拟ActiveRecord实例的模拟对象,在您的情况下,您实际上会丢弃该实例。与所有RSpec双打一样,第一个参数只是给它一个可用于错误消息等的名称。

是的,您返回true时收到错误的原因是redirect_to期望@user成为ActiveRecord实例,而它不仅仅是Hash如你所说的那样,它没有所需的model_name方法。

有很多方法可以重写这一点,特别是如果你想同时支持成功和失败的情况,但是最小化变化的一种方法是(未经过全面测试):

describe UsersController do
  let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo@email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }

  describe 'PATCH #update' do
    it "should fail in this case" do
      User.should_receive(:find).with(user.id.to_s).and_return user      
      user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true

      patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }

      flash[:error].should == "could not update user"
      response.status.should == 200
    end
  end
end