Hashes and expectations with RSpec (RED)

Today I wrote a pair of tests for my controller and found a couple of things I would like to understand in deep and begin to practice them more. While testing the #delete from my controller in my controller tests, I needed to test if at the end the count was changing or not. In order to achieve this I found that the expectation change was the one to go with.

I wrote my test like this:

  describe '#delete' do
    context 'authenticated admin' do
      before do
        @user = FactoryBot.create(:user, :admin)
        @item = FactoryBot.create(:meetings_item)
        @list = FactoryBot.create(:meetings_list)
      end
      it 'deletes an item' do
        sign_in(@user)
        expect do
          delete(:destroy, params: { id: @item.id, meetings_list_id: @item.id })
        end.to change(MeetingsItem, :count).by(-1)
      end
    end

What I tried to achieve is to proof that the MeetingsItem, :count, was changing by -1, which in this example passed on green. First of all I would like to understand or how can I precisely tell which are the params I need to introduce and use. I got confused first by this line: delete(:destroy, params: { id: @item.id, meetings_list_id: @item.id }) it makes sense to me that the meetings_list_id: is the ID of a LIST not from an ITEM. So then I took a look back at my model, controller and my factories:

My model:

class MeetingsItem < ApplicationRecord
  validates :date, :reason, presence: true

  belongs_to :meetings_list
  monetize :amount_cents, allow_nil: true, default: nil
  def completed?
    !completed_at.blank?
  end
end

here I noticed the relationship of meetings_list. Next I check the controller:

class MeetingsItemsController < ApplicationController
  before_action :set_meeting_list
  before_action :set_meetings_item, except: %i[create]

  def create
    @meetings_item = @meetings_list.meetings_items.create(meeting_item_params)
    if @meetings_item.valid?
      @meetings_item.save
      redirect_to @meetings_list, notice: 'Neuer Eintrag hinzugefügt'
    else
      render :new
    end
  end

  def new
    @meetings_item = MeetingsItem.new
  end

  def destroy
    flash[:notice] = if @meetings_item.destroy
                       'Listenelement gelöscht.'
                     else
                       'Listenelement kann nicht gelöscht werden.'
                     end
    redirect_to @meetings_list
  end

  def complete
    @meetings_item.update_attribute(:completed_at, Time.now)
    redirect_to @meetings_list, notice: 'Aufgabe erledigt'
  end

  private

  def set_meeting_list
    @meetings_list = MeetingsList.find(params[:meetings_list_id])
  end

  def set_meetings_item
    @meetings_item = @meetings_list.meetings_items.find_by(id: params[:id])
  end

  def meeting_item_params
    params[:meetings_item].permit(:date, :reason, :amount)
  end
end

My factory:

FactoryBot.define do
  factory :meetings_list do
    title       { Faker::Team.name }
    responsible { Faker::TvShows::SouthPark.character }
  end

  factory :invalid_meeting, parent: :meetings_list do
    title { nil }
  end

  factory :meetings_item do
    association :meetings_list
    date   { Date.today }
    reason { Faker::Dessert.variety }
    amount { Faker::Number.number(digits: 2) }

    trait :invalid_item do
      reason { nil }
    end
  end
end

I am assuming both: delete(:destroy, params: { id: *@item.id*, meetings_list_id: *@item.id* }) are using the item id from the factory id and for the meetings_list_id: through the association :meetings_list if taking the id from the list. Am I'm missing something? Because it is not clear to me.

Next I tried to test with the same hash an unauthenticated user with this test:

context 'unauthenticated user' do
      before do
        @user = FactoryBot.create(:user)
        @item = FactoryBot.create(:meetings_item)
      end

      it 'fails to delete' do
        expect do
          delete(:destroy, params: { id: @item.id,
                                     meetings_list_id: @item.id })
        end.to_not change(MeetingsItem, :count)
      end
    end

and it fails with:

Failures:

  1) MeetingsItemsController#delete unauthenticated user fails to delete
     Failure/Error:
       expect do
         delete(:destroy, params: { id: @item.id,
                                    meetings_list_id: @item })
       end.to_not change(MeetingsItem, :count)

       expected `MeetingsItem.count` not to have changed, but did change from 1 to 0

This is not what I expected since, this method first of all shouldn't be available without sign_in and should only be available to administrators. In the test none are present, and it simply deletes an item. Why?

What I'm trying to achieve is hit all the lines from my controller:

  def destroy
     flash[:notice] = if @meetings_item.destroy
                        'Listenelement gelöscht.'
                     else
                       'Listenelement kann nicht gelöscht werden.'
                     end
     redirect_to @meetings_list
  end

I do not get to hit the else statement. Maybe I'm testing in the wrong direction but I would like to know what I'm doing wrong.

No Comments Yet