【问题标题】:RSpec undefined method `update_attributes' for nil:NilClass ErrorRSpec未定义的方法`update_attributes'为nil:NilClass错误
【发布时间】:2012-05-26 11:02:44
【问题描述】:

我正在尝试测试我的 Rails 应用程序。现在我正在测试我的微型控制器,但我遇到了问题。 我使用设备进行身份验证。 我测试的控制器是这样的,

class WidgetsController < ApplicationController
  #skip_before_filter :authenticate, :only => [:new, :create]
  before_filter :authenticate, :except=>[:disabled]

  def index
    @widgets = current_user.widgets.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @widgets }
    end
  end

  def new
    @widget = Widget.new

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @widget }
    end
  end

  def create
    @widget = Widget.new(params[:widget])
    @widget.user_id = current_user.id
    @widget.score = 0
    @widget.total_score = 0
    @widget.click_number = 0
    @widget.average = 0

    respond_to do |format|
      if @widget.save
        format.html { redirect_to edit_widget_path(@widget), notice: 'Widget was successfully created.' }
        format.json { render json: @widget, status: :created, location: @widget }
      else
        format.html { render action: "new" }
        format.json { render json: @widget.errors, status: :unprocessable_entity }
        raise 'an error occurs when creation'
      end
    end
  end

  def show
    @widget = Widget.find_by_uuid(params[:uuid])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @widget }
    end
  end

  def edit
    @widget = Widget.find_by_uuid(params[:uuid])
  end

  def update
    @widget = Widget.find_by_uuid(params[:uuid])

    respond_to do |format|
      if @widget.update_attributes(params[:widget])
        format.html { redirect_to edit_widget_path(@widget), notice: 'Widget was successfully updated.' }
        format.json { render json: @widget, status: :created, location: @widget }
      else
        format.html { render action: "edit" }
        format.json { render json: @widget.errors, status: :unprocessable_entity }
        raise 'there is an error when alteration'
      end
    end
  end

  def destroy
    @widget = Widget.find_by_uuid(params[:uuid]).destroy

    respond_to do |format|
      format.html { redirect_to widgets_path }
      format.json { head :no_content }
    end
  end

  #generate widget
  def generate
    respond_to do |format|
      format.js {}
    end
  rescue
    #TODO add widget not found page
    render :template => 'application/widget_not_found', :status => :not_found
  end



  protected

  def authenticate
    unless current_user
      redirect_to root_url
    end
  end

end

我的路线文件也许你可以看看。

Rwidget::Application.routes.draw do

  match 'widgets' => 'widgets#index', :via => :get
  match 'widgets/new' => 'widgets#new', :via => :get
  match 'widgets' => 'widgets#create', :via => :post
  match 'widgets/:uuid' => 'widgets#show', :via => :get
  get 'widgets/:uuid/edit' => 'widgets#edit'
  match 'widgets/:uuid' => 'widgets#update', :via => :put
  match 'widgets/:uuid' => 'widgets#destroy', :via => :delete

  resources :widgets, :collection => [:destroy]

  match 'rws/generate/:uuid' => 'rws#generate'

  get 'rws/:uuid' => 'rws#show'
  put 'rws/:uuid' => 'rws#update'
  post 'rws/getUserInfo' => 'rws#getUserInfo'

  resources :authentications
  match '/auth/:provider/callback' => 'authentications#create'

  devise_for :users do
    get '/users/sign_out' => 'devise/sessions#destroy'
  end

  root :to => "home#index"

还有我的 controller_spec。 rb 是

require 'spec_helper'

describe WidgetsController do
  login_user

  describe "User" do
    before(:each) do
      @current_user = mock_model(User, :id => 1)
      @widget = mock_model(Widget, :user_id => 1)
      Widget.stub!(:current_user).and_return(@current_user)
      Widget.stub!(:widgets).and_return(@widget)
    end

    it "should have a current_user" do
      subject.current_user.should_not be_nil
    end
  end

  def mock_widget(stubs={})
    @mock_widget ||= mock_model(Widget, stubs).as_null_object
  end

  describe "Get index" do
    it "should get all widgets " do
      Widget.stub(:all) { [mock_widget] }
      get :index
      assigns(:widgets) == @widgets
    end
  end

  describe "Post widget" do
    it "creates a new widget" do
      Widget.stub(:new).with({'these' => 'params'}) { mock_widget(:language => "en") }
      post :create, :widget => {'these' => 'params'}
      assigns(:widget) == @widgets
      response.should redirect_to (edit_widget_path(@mock_widget))
    end
  end

  describe "Get widget" do
    it "shows exact widget via id" do
      Widget.stub(:find).with("10") { [mock_widget] }
      get :show
      assigns(:mock_widget) == mock_widget
    end
  end

  describe "Put widget" do
    it "updates the widget attributes" do
      Widget.stub(:find).with("6") { [mock_widget] }
      mock_widget.should_receive(:update_attributes).with({'these' => 'params'})
      put :update, :id => "6", :widget => {'these' => 'params'}
    end
  end

  describe "delete destroy" do
    it "destroys the requested widget" do
      Widget.stub(:find).with("10") { [mock_widget] }
      mock_widget.should_receive(:destroy)
      get :destroy, :id => "10"
    end
  end
end

我配置了我的规范助手来设计设置。 我的 controller_macros.rb 是

module ControllerMacros
  def login_user
    before(:each) do
      @request.env["devise.mapping"] = Devise.mappings[:user]
      user = Factory(:user)
      #user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
      sign_in user
    end
  end
end

还有我的 spec_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

RSpec.configure do |config|
  # ## Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr
  config.include Devise::TestHelpers, :type => :controller
  config.extend ControllerMacros, :type => :controller
  config.include RequestMacros, :type => :request
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

end

最后,我的 devise.rb 位于 support 文件下,

RSpec.configure do |config|
  config.include Devise::TestHelpers, :type => :controller
end

所以。当我调试我的 controller.spec 文件时,更新和删除描述块中没有 current_user。 当我运行ender$ rspec spec/controllers/widgets_controller_spec.rb 我面对

Failures:

  1) WidgetsController Put widget updates the widget attributes
     Failure/Error: put :update, :id => "6", :widget => {'these' => 'params'}
     NoMethodError:
       undefined method `update_attributes' for nil:NilClass
     # ./app/controllers/widgets_controller.rb:60:in `block in update'
     # ./app/controllers/widgets_controller.rb:59:in `update'
     # ./spec/controllers/widgets_controller_spec.rb:52:in `block (3 levels) in <top (required)>'

  2) WidgetsController delete destroy destroys the requested widget
     Failure/Error: get :destroy, :id => "10"
     NoMethodError:
       undefined method `destroy' for nil:NilClass
     # ./app/controllers/widgets_controller.rb:72:in `destroy'
     # ./spec/controllers/widgets_controller_spec.rb:60:in `block (3 levels) in <top (required)>'

Finished in 0.4569 seconds
6 examples, 2 failures

Failed examples:

rspec ./spec/controllers/widgets_controller_spec.rb:49 # WidgetsController Put widget updates the widget attributes
rspec ./spec/controllers/widgets_controller_spec.rb:57 # WidgetsController delete destroy destroys the requested widget

我该如何解决?

解决方案: 由于搜索 uuid,我将 findbyid 转移到 findbyuuid。之后,我需要返回一个数组,我就这样搞定了,

Widget.stub(:find_by_uuid).with("6").and_return(mock_widget(:update_attributes => true))

完成了!

【问题讨论】:

    标签: ruby-on-rails-3 controller rspec-rails


    【解决方案1】:

    在你的规范中你正在做

    Widget.stub(:find).with("6")...
    

    但是您的应用代码调用Widget.find_by_uuid。您的规范代码应该删除您的应用程序代码将执行的调用。同样,您的应用代码期望设置params[:uuid],但您的规范仅为params[:id]

    您还应该返回正确的返回值 - find_by 返回单个对象,但您设置的存根返回一个数组。

    Widget.stub(:find_by_uuid).with("6") { mock_widget}
    

    将使您的存根返回模拟小部件,而不是包含模拟小部件的数组

    【讨论】:

    • 我可以做 Widget.stub(:find_by_uuid).with("6")... 吗?
    • 试试吧!您可以存根任何方法 - 该方法甚至不需要存在。
    • 哇!它有效,谢谢!但现在它返回失败/错误: put :update, :id => "6", :widget => {'these' => 'params'} 收到 :find_by_uuid 预期有意外参数: ("6") 得到: (nil)
    • 就像我说的那样,您的规范将错误的参数传递给您的操作(id 而不是 uuid)
    • 不返回数组。所以只返回对象 - 查看更新的答案。
    【解决方案2】:

    您只需查看 WidgetCONtroller#update 即可了解调用了哪个对象“update_attributes”。

      def update
        @widget = Widget.find_by_uuid(params[:uuid])
    
        respond_to do |format|
          if @widget.update_attributes(params[:widget])
    

    所以,基本上,这个调用返回 nil:

    Widget.find_by_uuid(params[:uuid])
    

    您可以使用“rails 控制台”进行调试。

    【讨论】:

    • 它正在开发区工作。当我测试它时,出现“nil:NilClass 的未定义方法‘update_attributes’”的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 1970-01-01
    • 1970-01-01
    • 2016-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多