Search code examples
ruby-on-railsrspec-rails

Got a NoMethodError: undefined method `id' for nil:NilClass


Summary: I am new to Ruby on Rails. Was doing a class assignment for TDD using rspec for the edit feature. I encountered this error:

Failure/Error: visit "/categories/#{category.id}/edit" NoMethodError: undefined method `id' for nil:NilClass

What I have done is to define the edit and update methods in the CategoriesController but error persists.

Please refer to codes below. Thankful for your guidance.

edit_category_spec.rb:


RSpec.describe 'EditCategories', type: :system do
  before do
    driven_by(:rack_test)
  end

  it 'creates category, saves and shows newly created category' do
    # visit root route
    visit '/'
    #click create category link
    click_link 'Create Category'
    #visit categories/new page
    visit '/categories/new'
    #fill in form with required info
    fill_in 'Name', with: 'This is a category'
    #click submit button
    click_button 'Create Category'
    #expect page to have the content submitted
    expect(page).to have_content('This is a category')
  end

  it 'edits category, saves and shows edited category' do
    category = Category.order("id").last
    visit "/categories/#{category.id}/edit"
    fill_in 'Name', with: 'This is a category edited'
    click_button 'Create Category'
    expect(page).to have_content('This is a category edited')
  end
end  ```


categories_controller.rb
```class CategoriesController < ApplicationController
  def index
  end

  def show
    @category = Category.find(params[:id])
  end

  def new
    @category = Category.new
  end

  def create
    @category = Category.new(category_params)
    if @category.save
      redirect_to @category
    else
      render :new
    end
  end

  def edit
    @category = Category.find(params[:id])
  end

  def update
    @category = Category.find(params[:id])
    if @category.save
      redirect_to @category
    else
      render :edit
    end
  end

  private

  def category_params
    params.require(:category).permit(:name)
  end
end

Solution

  • Rails tests are fully independent (in theory) and don't maintain data between tests. So, if you create a Category in one test and then try to access it in another test (as you're doing here), it won't work. It works this way so that you don't get cascading failures but can test things discretely. Rails provides various methods to create the data you're then going to test. Fixtures are the simplest, but there are gems like FactoryGirl you can use, too.

    The easiest solution to your problem here is to combine these two test methods (it 'creates category, saves and shows newly created category' and it 'edits category, saves and shows edited category') into a single method that first creates and then edits the Category record.

    Better would be to keep them separate (because your test suite will soon become large and complex, and you'll need to separate them), and to use fixtures to set up your records before testing.

    Finally, when you're writing tests, don't forget to write tests that capture failure - your code should refuse to create or edit the record if the data provided isn't valid, for example.