0
votes

I'm a beginner working my way through Agile Web Development with Rails 4 where we (readers) build a shopping-cart style demo app. So, far the main REST resources are 1) products, 2) carts and 3) line items. This is my second demo/sample app on my path to learning Rails and everything seems straight-forward so far, except I'm consistently getting an error in my tests for the update action in the line items controller. I'm not sure whether it's an error related to the test itself, the actual controller action, and/or my fixtures settings. (I'm using the "built-in" MiniTest with fixtures for the first time; previous sample app I did used RSpec and FactoryGirl.)

The error (not failure) I'm getting looks like this:

Finished tests in 0.936959s, 26.6821 tests/s, 56.5660 assertions/s.

  1) Error:
LineItemsControllerTest#test_should_update_line_item:
ActionController::ActionControllerError: Cannot redirect to nil!
    app/controllers/line_items_controller.rb:54:in `block (2 levels) in update'
    app/controllers/line_items_controller.rb:52:in `update'
    test/controllers/line_items_controller_test.rb:38:in `block in <class:LineItemsControllerTest>'

25 tests, 53 assertions, 0 failures, 1 errors, 0 skips

I'm unclear why I'm redirecting to nil. Is the path bad? Or is it the object at the end of the path? And/or "other"? I don't readily see anything in the controller itself that looks like a problem (regardless, I've included the code below), so I suspect it has something to do with the test file or fixtures settings. I've tried filling in various 'dummy' (e.g., product_id: 55) and 'referenced' (e.g., product_id: products(:ruby).id) values in the line_items.yml fixtures with no success.

I've also tried different things within the controller test setup, such as referencing specific fixture IDs in other fixture types (products and carts) that line items belong to (same idea as above). I tried editing the update test in a number of ways as well, including trying a redirect to the line item's cart instead of the line_items_path, such as:

assert_redirected_to cart_path(assigns(:line_item).cart)

...instead of...

assert_redirected_to line_item_path(assigns(:line_item))

Also tried variations of defining :line_item, which I take to be the updated object, e.g.:

patch :update, id: @line_item, line_item: { product_id: products(:ruby).id }

...instead of...

patch :update, id: @line_item, line_item: { product_id: @line_item.product_id }

What I believe to be relevant code included below. Any help very much appreciated!

line_items_controller.rb

class LineItemsController < ApplicationController
  include CurrentCart
  before_action :set_cart, only: [:create]
  before_action :set_line_item, only: [:show, :edit, :update, :destroy]


  # GET /line_items
  # GET /line_items.json
  def index
    @line_items = LineItem.all
  end

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

  # GET /line_items/new
  def new
    @line_item = LineItem.new
  end

  # GET /line_items/1/edit
  def edit
  end

  # POST /line_items
  # POST /line_items.json
  def create
    product = Product.find(params[:product_id])
    @line_item = @cart.add_product(product.id)

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

    session[:count] = nil
  end

  # PATCH/PUT /line_items/1
  # PATCH/PUT /line_items/1.json
  def update
    respond_to do |format|
      if @line_item.update(line_item_params)
        format.html { redirect_to @line_item.cart,
          notice: 'Line item was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @line_item.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /line_items/1
  # DELETE /line_items/1.json
  def destroy
    @line_item.destroy
    respond_to do |format|
      format.html { redirect_to line_items_url }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def line_item_params
      params.require(:line_item).permit(:product_id)
    end
end

line_items_controller_test.rb

require 'test_helper'

class LineItemsControllerTest < ActionController::TestCase
  setup do
    @line_item = line_items(:one)
  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
    patch :update, id: @line_item, line_item: { product_id: @line_item.product_id }
    assert_redirected_to line_item_path(assigns(:line_item))
  end

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

    assert_redirected_to line_items_path
  end
end

line_items.yml

one:
  product_id: #Trying values here doesn't seem to help.
  cart_id: #Trying values here doesn't seem to help.

two:
  product_id: 
  cart_id: 

products.yml

one:
  title: MyString
  description: MyText
  image_url: MyString
  price: 9.99

two:
  title: MyString
  description: MyText
  image_url: MyString
  price: 9.99

ruby:
  title:        Programming Ruby 1.9
  description:
    Ruby is the fastest growing and most exicting dynamic
    language out there. If you need to get working programs
    delivered fast, you should add Ruby to your toolbox.
  price:        49.50
  image_url:    ruby.png

three:
  title: MyString
  description: MyText
  image_url: MyString
  price: 9.99  

carts.yml

one: {}
# column: value
#
two: {}
#  column: value
2

2 Answers

1
votes

In line_items_controller.rb you redirect back to the cart page using the Rails resource notation. The line item in question is :one which has no associated cart. Try assigning one by modifying line_item.yml like so:

one:
  product: one
  cart: one

I think Rails should be smart enough to know that you want Product and Cart, respectively, for these assignments, but if not, you might try using more descriptive / unique labels for your fixtures: :cart_one, :product_one or even :joes_cart, :bobs_cart, etc.

0
votes

You're line item has no id in line_items.yml. Make it like this

line_item.yml

one:
  id: 1
  product_id: #Trying values here doesn't seem to help.
  cart_id: #Trying values here doesn't seem to help.

two:
  id: 2
  product_id: 
  cart_id: 

You're also trying to pass the entire object instead of the id in the tests. Just pass the id :

patch :update, id: @line_item.id, line_item: { product_id: @line_item.product_id }

You shouldn't really be trying to post the entire object ever. Post the object id instead