未定义的方法`map'错误应该创建模型测试

时间:2016-05-14 21:24:43

标签: ruby-on-rails testing view functional-testing

应用/控制器/ products_controller.rb:

class ProductsController < ApplicationController
    before_action :set_product, only: [:show, :edit, :update, :destroy]
    before_action :authenticate_user!, except: [:index, :show]

    # GET /products
    # GET /products.json
    def index
        #@products = Product.all
        @products = Product.all
        if params[:search]
            @products = Product.search(params[:search]).order("created_at DESC").paginate(page: params[:page], per_page: 5)
        else
            @products = Product.all.order('created_at DESC').paginate(page: params[:page], per_page: 5)
        end

        if session[:cart] then
            @cart = session[:cart]
        else
            @cart = {}
        end

    end

    # GET /products/1
    # GET /products/1.json
    def show
    end

    # GET /products/new
    def new
        if current_user.admin?
            #@product = Product.new
            @product = Product.new
            @categories = Category.all.map{|c| [ c.name, c.id ] }
        end
    end

    # GET /products/1/edit
    def edit
        if current_user.admin?
             @categories = Category.all.map{|c| [ c.name, c.id ] }
        end
    end

    # POST /products
    # POST /products.json
    def create
        if current_user.admin?
            @product = Product.new(product_params)
            @product.category_id = params[:category_id]

            respond_to do |format|
                if @product.save
                format.html { redirect_to @product, notice: 'Product was successfully created.' }
                format.json { render :show, status: :created, location: @product }
                else
                format.html { render :new }
                format.json { render json: @product.errors, status: :unprocessable_entity }
                end
            end
        end
    end

    # PATCH/PUT /products/1
    # PATCH/PUT /products/1.json
    def update
        if current_user.admin?
            @product.category_id = params[:category_id]
            respond_to do |format|
                if @product.update(product_params)
                format.html { redirect_to @product, notice: 'Product was successfully updated.' }
                format.json { render :show, status: :ok, location: @product }
                else
                format.html { render :edit }
                format.json { render json: @product.errors, status: :unprocessable_entity }
                end
            end
        end
    end

    # DELETE /products/1
    # DELETE /products/1.json
    def destroy
        if current_user.admin?
            @product.destroy
            respond_to do |format|
                format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
                format.json { head :no_content }
            end
        end
    end

    private
    # Use callbacks to share common setup or constraints between actions.
    def set_product
        @product = Product.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def product_params
        params.require(:product).permit(:title, :description, :photo, :price, :category, :subcategory)
    end
end

应用/视图/产品/ new.html.erb:

<h1>New Product</h1>

<%= render 'form' %>

<%= link_to 'Back', products_path %>

应用/视图/产品/ _form.html.erb:

<%= form_for(@product, multipart: true) do |f| %>
  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>

      <ul>
      <% @product.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="field">
    <%= f.label :image %><br>
    <%= f.file_field :photo %>
  </div>
  <div class="field">
    <%= f.label :price %><br>
    <%= f.number_field :price, :step => "0.01" %>
  </div>
  <div class="field">
    <%= f.label :category %><br>
    <%= select_tag(:category_id, options_for_select(@categories), :prompt => "Select one!") %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

我在 test / controllers / products_controller_test.rb中的测试:

  test "should create product" do
    sign_in users(:admin)
    assert_difference('Product.count') do
      post :create, product: { category_id: @product.category, description: @product.description, photo_content_type: @product.photo_content_type, photo_file_name: @product.photo_file_name, photo_file_size: @product.photo_file_size, photo_updated_at: @product.photo_updated_at, price: @product.price, title: @product.title }
    end

    assert_redirected_to product_path(assigns(:product))
  end

Causing this error:

Finished in 0.816541s, 24.4936 runs/s, 35.5157 assertions/s.

  1) Error:
ProductsControllerTest#test_should_create_product:
ActionView::Template::Error: undefined method `map' for nil:NilClass
    app/views/products/_form.html.erb:32:in `block in _app_views_products__form_html_erb__4489093195482743578_70189257075560'
    app/views/products/_form.html.erb:1:in `_app_views_products__form_html_erb__4489093195482743578_70189257075560'
    app/views/products/new.html.erb:3:in `_app_views_products_new_html_erb__3438187527367239596_70189257283820'
    app/controllers/products_controller.rb:57:in `block (2 levels) in create'
    app/controllers/products_controller.rb:52:in `create'
    test/controllers/products_controller_test.rb:29:in `block (2 levels) in <class:ProductsControllerTest>'
    test/controllers/products_controller_test.rb:28:in `block in <class:ProductsControllerTest>'

20 runs, 29 assertions, 0 failures, 1 errors, 0 skips

我正在使用rake test:functionals运行我的测试。其他测试都没问题,但只有这个测试才有问题。

app / views / products / _form.html.erb:中甚至没有任何名为map的方法。所以我不知道这个错误的原因。

如果您想查看整个项目:https://github.com/mertyildiran/SCOR

变化:

根据答案:

我在表单中进行了此更改:<%= f.collection_select(:category_id, Category.all, :id, :name) %>

在产品控制器中:params.require(:product).permit(:title, :description, :photo, :price, :category_id)

新版测试:

  test "should create product" do
    sign_in users(:admin)
    image = fixture_file_upload "../../public/assets/products/1/original/" + @product.photo_file_name
    puts image.inspect
    assert_difference('Product.count') do
      post :create, product: { category_id: @product.category, description: @product.description, photo: image, price: @product.price, title: @product.title }
    end

    assert_redirected_to product_path(assigns(:product))
  end

我们摆脱了错误,但失败仍然存在,stdout:

# Running:

.......#<Rack::Test::UploadedFile:0x007f5a1894fdf8 @content_type=nil, @original_filename="ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg", @tempfile=#<Tempfile:/tmp/ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg20160515-31919-1j7oypa>>
F............

Finished in 0.800235s, 24.9926 runs/s, 37.4890 assertions/s.

  1) Failure:
ProductsControllerTest#test_should_create_product [/home/mertyildiran/Documents/SCOR/test/controllers/products_controller_test.rb:25]:
"Product.count" didn't change by 1.
Expected: 3
  Actual: 2

20 runs, 30 assertions, 1 failures, 0 errors, 0 skips

验证失败原因:

<div id="error_explanation">
  <h2>2 errors prohibited this product from being saved:</h2>

  <ul>
    <li>Photo content type is invalid</li>
    <li>Photo is invalid</li>
  </ul>
</div>

已修复此image.content_type = @product.photo_content_type

2 个答案:

答案 0 :(得分:1)

当你在测试中调用post :create, product: ...时,验证失败会导致渲染新视图。

new视图会因调用options_for_select(@categories)而导致错误,但未定义@categories - 换句话说niloptions_for_select期望参数为数组,并在.map上调用nil

这不是唯一的问题 - 你也没有正确地嵌套输入。

<%= select_tag(:category_id, options_for_select(@categories), :prompt => "Select one!") %>

最终会在params[:category_id]而不是params[:product][:category_id]

相反,您要做的是使用collection_select并将其绑定到模型:

<div class="field">
  <%= f.label :category_id %><br>
  <%= f.collection_select(:category_id, Category.all, :id, :name) %>
</div>

您还应该确保将正确的参数列入白名单 - 如果您很难跟踪它们,请确保您也进行测试!

def product_params
  params.require(:product).permit(:title, :description, :photo, :price, :category_id, :subcategory)
end

如果要准确调试哪个属性导致验证失败,可以选择以下几个选项:

  • assigns(:product)会从您的控制器中提供@product实例变量。 assert_equals(@product.errors.full_messages, [])为您提供了一个列表。你必须先调用控制器动作!
  • 您可以执行assert_equals(@response.body, '')转换页面HTML。你必须先调用控制器动作!
  • 使用$ tail -f logs/test.log
  • 在日志上保留一个标签

答案 1 :(得分:0)

在堆栈跟踪中,当尝试保存@product并且控制器想要呈现new模板时,似乎验证失败。但是,您没有为此方案定义@categories变量,这可能是map nil来自@categories的调用。

因此,您可能应该执行以下两项操作:

  • create数组添加到控制器中update<span>操作的失败保存部分,以便失败保存方案正常运行
  • 修复测试代码,以便在创建产品时验证不会失败。