新手问题 rspec-rails: 好端端的 Model 怎么成了 NilClass?

merlinran · 2012年07月07日 · 最后由 fenprace 回复于 2012年07月09日 · 4076 次阅读

见了鬼了。@contest明明有值,还 respond_to save,下一条里就成 NilClass 了。是我对 Ruby 的理解有误么?

# coding: utf-8
class ContestsController < ApplicationController
  def create
    @contest = Contest.new(params[:contest])
    p '@contest supports save' if @contest.respond_to?(:save)
    if @contest.save
      flash[:notice] = '创建成功'
      redirect_to home_path
    else
      flash[:error] = '创建失败'
      render :action => :new
    end
  end
end

输出

Running: spec/controllers/contests_controller_spec.rb spec/models/contest_spec.rb
F"@contest supports save"
F*F

Pending:
  ContestsController POST create redirects to add mission page
    # Not yet implemented
    # ./spec/controllers/contests_controller_spec.rb:16

Failures:

  1) ContestsController POST create creates a contest
     Failure/Error: post :create, contest: { 'name' => contest.name }
     NoMethodError:
       undefined method `save' for nil:NilClass
     # ./app/controllers/contests_controller.rb:6:in `create'
     # ./spec/controllers/contests_controller_spec.rb:9:in `block (3 levels) in <top (required)>'

补上 spec

describe ContestsController do
  let(:my_contest) { FactoryGirl.create(:contest) }
  before(:each) { my_contest }
  describe 'POST create' do
    it 'creates a contest' do
      Contest.should_receive(:new).with(name: my_contest.name)
      post :create, contest: { name: my_contest.name }
    end
    it 'saves the contest' do
      Contest.stub(:new).and_return(my_contest)
      my_contest.should_receive(:save)
      post :create
    end
    it 'redirects to add mission page'
  end
end

你确定@contest你拼写对了吗?

你的 spec 呢

#1 楼 @leomayleomay 我开始也怀疑拼写问题,还反复检查了几次,后来用 vi 直接 y2w 再 p 的,不至于出错 #2 楼 @fredwu 补上 spec 了

加个断点,看看@contest是不是为空

几点问题:

  1. 你已经 let(:my_contest) 了,就不要 before 了。另外,所有的 before(:each) 都可以简写成 before
  2. 你的 controller 测试的太细了,不方便重构。直接测试 response(有无 exception)以及创建的 contest 就可以了。
  3. FactoryGirl 有 FactoryGirl::Syntax::Methods,include 后就不用每次都打 FactoryGirl 了。

最后一点,也就是为什么会是 NilClass 的问题——

你第一个测试里给 post 传递了 params,但第二个测试里没有…… 没有 params 当然就找不到 contest 了!

Controller 里,除非特殊状况,根本不需要 stub 你的 Model。Model 应该在 unit test 里单独进行详细的测试。

#5 楼 @fredwu 多谢耐心指点

  1. 加 before 是因为 let 只有在调用时才求值,遇到过因此造成的怪异现象(这个 spec 中也遇到,尚需排查)。后来才发现 let! 也能达到这一效果。
  2. 是跟着 The RSpec Book 的风格,我也觉得过细了一点,还需慢慢找感觉。
  3. 多谢

但 NilClass 的问题,报错的其实是第一个测试,似乎不是这个原因…… 而且也解释不了上一条语句有值,下一条就 NilClass

刚刚试着改成

Contest.should_receive(:new).with('name' => 'CodeMatch')
post :create, contest: { 'name' => 'CodeMatch' }

之后,spec 就通过了。

看来是我对 FactoryGirl 的理解不对,得再复习一下。谢谢各位,有了结果,我会回复上来。

@huacnlee 这贴爆 Bug 了!代码也被转为 Emoji 了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号