【问题标题】:Rspec & Capybara: ActionView::Template::Error: undefined method `' for nil:NilClassRspec & Capybara: ActionView::Template::Error: undefined method `' for nil:NilClass
【发布时间】:2017-08-28 09:32:38
【问题描述】:

我知道ActionView::Template::Error: undefined method [] for nil:NilClass 是一个常见错误,但其他页面似乎都没有回答我的问题。我有一个非常简单的测试,登录后进入新项目页面并通过填写表格创建一个新项目。但是当我登录并在 Rspec / Capybara 中加载仪表板时,Rails 无法正确读取页面,并且页面在我们测试表单之前就失败了。

Rspec / Capybara 似乎不知道什么类项目并认为它是 NilClass,尽管登录页面上的设计表单没有问题。

这是规范文件:

require 'rails_helper'

describe "Creating a new project" do

  let!(:client) { Client.create(name: "abc", code: "abc") }
  let!(:project) { Project.create(name: "abc", client: client) }
  let!(:user) { User.create(email: "derp@example.com", password: "123123123") }

  # Sign In with user
  before :each do
    visit root_url
    fill_in 'user[email]', with: 'derp@example.com'
    fill_in 'user[password]', with: '123123123'
    find('input[name="commit"]').click
  end

  it "saves new project and returns to list of projects on root" do
    visit root_url
    click_on 'New Project'
    expect(current_path).to eq(new_project_path)

    fill_in "Name", with: "My New Cool Project"
    fill_in "Details", with: "Praise the sun!"
    select "urgency_fire", :from => "Urgency"
    select "status_in_progress", :from => "Status"
    select "ABC - abc", :from => "Client"
    fill_in "Due date", with: Time.now.to_s
    find('input[name="commit"]').click

    expect(current_path).to eq(root_path)
    expect(page).to have_text("My New Cool Project")

  end
end

这是测试输出:

1) Creating a new project saves new project and returns to list of projects on root
 Failure/Error: %td= project.client.code

 ActionView::Template::Error:
   undefined method `code' for nil:NilClass
 # ./app/views/dashboard/index.html.haml:17:in `block in _app_views_dashboard_index_html_haml__3165971454273894605_70112485977840'
 # ./app/views/dashboard/index.html.haml:15:in `_app_views_dashboard_index_html_haml__3165971454273894605_70112485977840'
 # ./spec/features/new_project_form_spec.rb:14:in `block (2 levels) in <top (required)>'
 # ------------------
 # --- Caused by: ---
 # NoMethodError:
 #   undefined method `code' for nil:NilClass
 #   ./app/views/dashboard/index.html.haml:17:in `block in _app_views_dashboard_index_html_haml__3165971454273894605_70112485977840'

这是仪表板控制器:

class DashboardController < ApplicationController
  before_action :authenticate_user!
  def index
     @projects = Project.includes(:client).all
  end
end

这里是仪表板 html (haml):

.container
  .row
    .col-sm-12
      %p= link_to 'New Project', new_project_path, title: 'New Project'
        %table
          %thead
            %tr
              %th Client
              %th Project   
              %th Urgency   
              %th Status    
              %th Due Date  
              %th Assigned To   
          %tbody
          - @projects.each do |project|
            %tr
              %td= project.client.code
              %td= link_to project.name, project
              %td= project.urgency 
              %td= project.status 
              %td= project.due_date 
              %td= project.user.count

我已经设置了路线,所以如果你没有登录,你会看到登录页面,如果你登录了,你会看到仪表板:

Rails.application.routes.draw do

    # devise gem routes
    devise_for :users

    # Dashboard
    authenticated :user do
        root :to => 'dashboard#index'
    end

    # Login
    devise_scope :user do
        root to: "devise/sessions#new"
    end

    # Projects
    resources :projects

    # Send all unknown pages to root
    if ENV["RAILS_ENV"] == "production"
        get '*path' => redirect('/')
    end

end

【问题讨论】:

    标签: rspec capybara ruby-on-rails-5


    【解决方案1】:

    错误提示没有project 对象。您正在使用 let! 调用来创建它。正在收集let! 变量以在example 块中执行。以您现在的方式使用它们有点棘手,因为它们可能不会被实例化,直到 example 块开始并且您明确地使用此代码为 before :each 示例。但是,在这种情况下,您不必使用 let。由于您没有引用测试中的值,因此您可以尝试以下操作:

    # Given an existing project and logged-in user
    before :each do
      project = Project.create(name: 'abc',
        client: Client.create(name: "abc", code: "abc"),
        code: 'abc')
      user = User.create(email: "derp@example.com", password: "123123123")
    
      visit root_url
      fill_in 'user[email]', with: 'derp@example.com'
      fill_in 'user[password]', with: '123123123'
      find('input[name="commit"]').click
    end
    

    From the docs: Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.

    【讨论】:

    • 感谢 Greg,这已解决!
    【解决方案2】:

    这与 Capybara 没有正确读取页面无关,而是表明您有一个 Project 对象,但没有与之关联的客户端。根据您的代码,最可能的原因是尝试创建 Client 对象时模型验证失败。您应该切换到create!,因为如果创建失败会立即通知您一个问题,而create 只会返回一个未保存的对象。此外,在创建客户端和项目时检查您的 test.log 以获取有关验证失败的警告。

    您的测试中还有一些其他问题需要解决,以避免继续进行不稳定的测试。

    1. 您的 before 块应该在末尾有一个断言,以确保在调用下一次访问之前登录已经完成。类似的东西

      expect(page).to have_content('You are now logged in!')
      

      或

      expect(page).to have_current_path('whatever path the user should be directed to on successful login')
      
    2. 切勿将eq 匹配器与current_path 一起使用。一旦你使用 eq 和 current_path 转移到支持 JS 的驱动程序,将导致各种测试不稳定。而是使用 Capybara 提供的 have_current_path 匹配器,它具有内置的等待/重试行为。所以你应该这样做而不是expect(current_path).to eq(new_project_path)

      expect(page).to have_current_path(new_project_path)
      

    【讨论】:

    • 感谢托马斯的测试帮助!我是编写测试的新手,所以非常感谢您的反馈!
    猜你喜欢
    • 2015-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多