Search code examples
ruby-on-railsrubytestunit

Agile Web Development with Rails: Couldn't find Product with id=1


My destroy unit test for line_item model is failing with the error "Couldn't find Product with id=1". It seems that Rails can't destroy my line_item because it throws an exception when getting it from the database. Here is my LineItem model:

class LineItem < ActiveRecord::Base
  belongs_to :product
  belongs_to :cart
  
  def total_price
    product.price * quantity
  end
end

And here is the test suite:

require 'test_helper'

class LineItemsControllerTest < ActionController::TestCase
  setup do
    @line_item = line_items(:one)
    Rails.logger.debug(@line_item.to_yaml)
    Rails.logger.debug(Product.all.to_yaml)
  end

  test "should get index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:line_items)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should create line_item" do
    assert_difference('LineItem.count') do
      post :create, product_id: products(:ruby).id
    end

    assert_redirected_to cart_path(assigns(:line_item).cart)
  end

  test "should show line_item" do
    get :show, id: @line_item
    assert_response :success
  end

  test "should get edit" do
    get :edit, id: @line_item
    assert_response :success
  end

  test "should update line_item" do
    put :update, id: @line_item, line_item: @line_item.attributes
    assert_redirected_to line_item_path(assigns(:line_item))
  end

  test "should destroy line_item" do
    Rails.logger.debug "Breaking!"
    assert_difference('LineItem.count', -1) do
      delete :destroy, id: @line_item
    end

    assert_redirected_to cart_path(path)
  end
end

And here is the part that I have logged:

    Breaking!
  [1m[35m (0.1ms)[0m  SELECT COUNT(*) FROM "line_items" 
Processing by LineItemsController#destroy as HTML
  Parameters: {"id"=>"980190962"}
  [1m[36mLineItem Load (0.1ms)[0m  [1mSELECT "line_items".* FROM "line_items" WHERE "line_items"."id" = ? LIMIT 1[0m  [["id", "980190962"]]
  [1m[35mProduct Load (0.1ms)[0m  SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1  [["id", 1]]
Completed 500 Internal Server Error in 3ms
  [1m[36m (0.1ms)[0m  [1mrollback transaction[0m
  [1m[35m (0.1ms)[0m  begin transaction
  [1m[36mLineItem Load (0.1ms)[0m  [1mSELECT "line_items".* FROM "line_items" WHERE "line_items"."id" = ? LIMIT 1[0m  [["id", 980190962]]
--- !ruby/object:LineItem
attributes:
  id: 980190962
  product_id: 1
  cart_id: 1
  created_at: 2012-05-25 20:37:17.000000000 Z
  updated_at: 2012-05-25 20:37:17.000000000 Z
  quantity: 1
  product_price: 

  [1m[35mProduct Load (0.2ms)[0m  SELECT "products".* FROM "products" 
---
- !ruby/object:Product
  attributes:
    id: 207281424
    title: Programming Ruby 1.9
    description: Ruby is the fastest growing and most exciting dynamic language out
      there.  If you need to get working programs delivered fast, you should add Ruby
      to your toolbox.
    image_url: ruby.png
    price: 49.5
    created_at: 2012-05-25 20:37:17.000000000 Z
    updated_at: 2012-05-25 20:37:17.000000000 Z  

EDIT: here is the Product model:

class Product < ActiveRecord::Base
  has_many :line_items
  
  before_destroy :ensure_not_referenced_by_any_line_item
  
  # Validation
  validates :title, :description, :image_url, presence: true
  validates :title, length: {
    minimum: 10,
    message: "must be at least %{count} characters long"
  }
  validates :price, numericality: { greater_than_or_equal_to: 0.01 }
  validates :title, uniqueness: true
  validates :image_url, allow_blank: true, format: {
    with: %r{\.(gif|jpg|png)$}i,
    message: 'must be a URL for GIF, JPG or PNG image.'
  }
  
  private
  
  def ensure_not_referenced_by_any_line_item
    if line_items.empty?
      return true
    else
      errors.add :base, 'Line items present'
      return false
    end
  end
end

Solution

  • This is one of the reasons that factories are generally favored over fixtures in the rails community. Fixtures don't automatically load their associations and tend to be brittle because of it. Your fixture has a product_id of 1, but that product doesn't exist.

    I'm not sure exactly how to fix your particular issue, but I would suggest you either:

    1. Create a Product with id=1 in your test.
    2. Create a Product fixture with id=1 and load it in your setup
    3. Switch to factories (preferably this one)

    EDIT

    As described here, you could also use label references for associations. So if you had a product fixture called 'tv', you could delete your line_item fixture's product_id field and replace it with product: tv