Hashes and expectations with RSpec (GREEN)

My previews post was about testing my controllers and tried to understand why they were not passing and the alternatives I had. With the help of my mentor I manage to understand how my test was working, why it was failing and of course new questions came by, one of them: How can I learn to debug?

I will begin first by clarifying what I learn and how my controller test passed on green at the end.

First things first:

describe '#delete' do
    context 'authenticated' do
      let(:user) { FactoryBot.create(:user, :admin) }
      let(:item) { FactoryBot.create(:meetings_item) }
      # let! is not LAZY!  ( I will explain later more about this line)

      it 'deletes an item' do
        sign_in(user)
        expect do
          delete(:destroy, params: { id: item.id,
                                     meetings_list_id: item.meetings_list.id })
        end.to_not change(MeetingsItem, :count)
        expect(flash[:notice]).to match(/Item erfolgreich vernichtet./)
        expect(response).to redirect_to("/meetings_lists/#{item.meetings_list.id}")
      end
    end

My previews post began with not knowing how the parameters were meant to be filled, which lead me to have confusions about it and not understanding what was happening, I was just following an example I found at the Everyday Rails Rspec tutorial book, but I was not understanding why these parameters work:

delete(:destroy, params: { id: item.id, meetings_list_id: item.meetings_list.id }

first of all, I was not even sure where the params came from, I was totally confused where it came. So for this my mentor introduced me to debugging and this help me know how to write the proper parameters. Just to notice, in my previews post the parameter meetings_list_id: item.id was wrong.

We checked the controller:

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

here was the first path to understand, I had this before actions in my controller and I wrote the methods as private as:

private

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

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

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

The #set_meeting_list and the #set_meetings_item were part of this before action, in specific I was working on the #destroy method so this was required. Next thing, the puts params[:meetings_list_id] and puts params[:id] this were keys that helped me understand the debugging and where it was failing and what was it throwing. When I ran my test it return for example:

  #delete
    authenticated
1
1

then I understood where this 1 were coming from and how I needed to change the method on my rspec. So back to this line again:

delete(:destroy, params: { id: item.id, meetings_list_id: item.meetings_list.id }

the id: was collected from the factory let(:item) { FactoryBot.create(:meetings_item) } and the meetings_list_id: from the same item factory. My MeetingsItem model has a dependency with the MeetingsList model belongs_to :meetings_list so the way to gather the MeetingsList.id was through the method: item.meetings_list.idand PUM! we got the list id I was looking for.

And last but not least, how do blocks evaluate was a new lesson I learned after this mentoring session. Here, the example:

    expect do
          delete(:destroy, params: { id: item.id,
                                     meetings_list_id: item.meetings_list.id })
        end.to_not change(MeetingsItem, :count)

here the expect is next to a do / end block, I've seen it written also with braces {} but this is up to you, I prefer the do end because makes more sense and is clear what is happening inside of this block. My previews post had this block:

    expect do
          delete(:destroy, params: { id: item.id,
                                     meetings_list_id: item.meetings_list.id })
        end.to change(MeetingsItem, :count).to(-1)

Notice the last lane: to change(Meeting...).to(-1) I was totally confused, why is this not deleting the item from the list? and throws me an failure that it didn't change from 0 to -1, that it actually remained as 0. My mentor later explained me why this was not working and this is what I understood:

1: The block began with an already created item from my factory: let(:item) { FactoryBot.create(:meetings_item) }

2: The block starts, and no item or list is present, so our count: goes by 0, then, one item is created so goes count by 1. before ending the block, the item is deleted, so our count: goes back to 0. Block ends and evaluates if it changed. What happened? Nothing changed it remained as 0 so the test was not doing a minus but was actually giving the right answer.

3: Changed the last lane of the test to: end.to_not change(MeetingsItem, :count)

4: Green! muahahaha!

So yes, it was creating an item, and then deleting it. So the test was working but I was not sure of what was happening inside of that block. Remember this first line I mentioned before?

let(:item) { FactoryBot.create(:meetings_item) }
  # let! is not LAZY!  ( I will explain later more about this lane)

Since let is a works with a lazy method, it can be override with a bang(!) at the end of the let.

let!(:item) { FactoryBot.create(:meetings_item)}

    expect do
          delete(:destroy, params: { id: item.id,
                                     meetings_list_id: item.meetings_list.id })
        end.to change(MeetingsItem, :count).to(0)

Lots of things to practice but also lots of things learned!

No Comments Yet