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.id
and 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!