Reputation: 105
I know the ActionView::Template::Error: undefined method [] for nil:NilClass
is a common error but none of the other pages seem to to answer my issue. I have a very simple test that logs in, goes to the new project page and makes a new project by filling in the form. But When I log in and rails loads the dashboard in Rspec / Capybara does not read the page correctly and the page fails before we even test the form.
Rspec / Capybara does not seem to know what class project and thinks its a NilClass, though it has no issue with the devise form on the login page.
Here is the spec file:
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: "[email protected]", password: "123123123") }
# Sign In with user
before :each do
visit root_url
fill_in 'user[email]', with: '[email protected]'
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
Here is the test output:
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'
Here is the dashboard controller:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
@projects = Project.includes(:client).all
end
end
And here is dashboard 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
And I have the route set up so if your not logged in you see the log in page, and if you are logged in you see the dashboard:
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
Upvotes: 2
Views: 1826
Reputation: 49910
This has nothing to do with Capybara not reading the page correctly, rather, would indicate that you have a Project object but there is no client associated with it. Most likely reason, given your code, is failing model validation when attempting to create the Client object. You should switch to create!
since that will raise if the create fails, immediately informing you of an issue, whereas create
will just return an unsaved object. Additionally check your test.log for warnings about validation failures when creating your Client and Project.
You have a couple of other issues in your test that you should fix to avoid flaky tests going forward.
Your before block should have an assertion at the end to make sure the login has completed before the next visit is called. Something like
expect(page).to have_content('You are now logged in!')
or
expect(page).to have_current_path('whatever path the user should be directed to on successful login')
Never use the eq
matcher with current_path
. Once you move to a JS capable driver using eq with current_path will lead to all sorts of test flakiness. Instead use the Capybara provide have_current_path
matcher which has waiting/retrying behavior built-in. So instead of expect(current_path).to eq(new_project_path)
you should do
expect(page).to have_current_path(new_project_path)
Upvotes: 1
Reputation: 1642
The error is suggesting that there there is no project
object. You are creating that with your let!
calls. The let!
variables are being collected for execution within the example
blocks. It is a little tricky to use them the way you are as they may not be instantiated until an example
block starts and you are explicitly before :each
example with this code. However, you don't have to use let
in this case. Since you do not reference the values in the tests you can try something like:
# 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: "[email protected]", password: "123123123")
visit root_url
fill_in 'user[email]', with: '[email protected]'
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.
Upvotes: 2