ruby-on-railsunit-testingparameters

Rails testing - how to pass params with foreign keys


I'm trying to write a test for a controller. The "create" action expects form data and rejects as "unprocessable entity" if the form data doesn't validate:

Failure:
ContactsControllerTest#test_should_post_create [test/controllers/contacts_controller_test.rb:36]:
Expected response to be a <3XX: redirect>, but was a <422: Unprocessable Entity>

I'm including model, controller, test-controller and fixtures below. Authorization is still out of the box but shouldn't be a factor here (other tests run fine).

I suspect that the foreign keys in the params are the culprit here but I don't know how to check for this. I also tried @saluation.id and @debitor.id in the test params.

Model

class Contact < ApplicationRecord
    validates :last_name, presence: true
    validates :email, format: {
        with: URI::MailTo::EMAIL_REGEXP
    }
    belongs_to :salutation
    belongs_to :debitor

Controller

class ContactsController < ApplicationController

  def create
      @contact = Contact.new(contact_params)
      @debitors = Debitor.all
      @salutations = Salutation.all
    
      if @contact.save 
        redirect_to contacts_path
      else
        render :new, status: :unprocessable_entity
      end
  end

  private
  def contact_params
    params.require(:contact).permit(:last_name, :first_name, :email, :salutation_id, :debitor_id)
  end

end

Test

require "test_helper"

class ContactsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:lazaro_nixon)
    @contact = contacts(:one)
    @salutation = salutations(:one)
    @debitor = debitors(:one)
  end


  test "should post create" do
    sign_in_as @user

    post contacts_url, :params => { :contact => { last_name: "Lastname", first_name: "Firstname", email: "test@test.com", salutation: @salutation, debitor: @debitor }}
    assert_response :redirect
    follow_redirect!
    assert_response :success
  end

end

Fixtures - contacts

one:
  last_name: MyString
  first_name: MyString
  email: MyString
  salutation: one
  debitor: one

Fixtures - salutations

one:
  salutation: MyString

Fixtures - debitors

one:
  company: MyString
  street: MyString
  house_number: MyString
  zip: 1
  city: MyString
  payment_terms: one

Solution

  • You need to pass the id of the record and not the entire record and use the same name of the keys as the expected parameter.

    require "test_helper"
    
    class ContactsControllerTest < ActionDispatch::IntegrationTest
      setup do
        @user = users(:lazaro_nixon)
        @contact = contacts(:one)
        @salutation = salutations(:one)
        @debitor = debitors(:one)
      end
    
      test "should post create" do
        sign_in_as @user
        # - don't mix in a bunch of hashrockets
        # - use linebreaks for readibility
        post contacts_url, params: { 
          contact: { 
            last_name: "Lastname", 
            first_name: "Firstname", 
            email: "test@test.com", 
            salutation_id: @salutation.id, 
            debitor_id: @debitor.id
          }
        }
        assert_response :redirect
        follow_redirect!
        assert_response :success
      end
    end
    

    But the test could use a bit of improvement so that it actually tests the important aspects of the controller action.

    # be descriptive
    test "should create contact with valid params" do
      sign_in_as @user 
      assert_difference ->{ Contact.count }, 1 do
        post contacts_url, 
          params: { 
            contact: { 
              last_name: "Lastname", 
              first_name: "Firstname", 
              email: "test@test.com", 
              salutation_id: @salutation.id, 
              debitor_id: @debitor.id
            }
          }
      end
      assert_redirected_to Contact.last
    end
    

    This tests that the record is actually created and that the location is the last created record instead of just the controller redirecting you to an arbitrary path which leaves you wide open to false positives.