I’m trying to refactor and do a cleanup exercise from this repo and this is my solution:
RSpec.describe Invitation do
# this is the helper
def create_user_and_build_invitation
team_owner = User.create
team = Team.create name: "A fine team", owner: team_owner
team_owner.update team: team
new_user = User.create(email: 'rookie@example.com')
[new_user, Invitation.new(team: team, user: new_user)]
end
# some lines below are deleted
describe "callbacks" do
describe "after_save" do
context "with valid data" do
it "invites the user" do
new_user, invitation = create_user_and_build_invitation
invitation.save
expect(new_user).to be_invited
end
end
context "with invalid data" do
it "does not save the invitation" do
new_user, invitation = create_user_and_build_invitation
invitation.team = nil
invitation.save
expect(invitation).not_to be_valid
expect(invitation).to be_new_record
end
# ...
end
end
end
describe "#event_log_statement" do
context "when the record is saved" do
it "include the name of the team" do
new_user, invitation = create_user_and_build_invitation
invitation.save
log_statement = invitation.event_log_statement
expect(log_statement).to include("A fine team")
end
# ...
end
context "when the record is not saved but valid" do
it "includes the name of the team" do
new_user, invitation = create_user_and_build_invitation
log_statement = invitation.event_log_statement
expect(log_statement).to include("A fine team")
end
# ...
end
context "when the record is not saved and not valid" do
it "includes INVALID" do
new_user, invitation = create_user_and_build_invitation
invitation.user = nil
log_statement = invitation.event_log_statement
expect(log_statement).to include("INVALID")
end
end
end
end
and I have some questions:
Do you have any advice to improve the code above?
Do I need to split the helper into two: one for generating the invitation and the second for generating the new_user?
Why this cleanup can decrease test running time? I mean the helper is creating some variable and called in each test anyway so I don’t feel like this brings more performance to the test (as far as I know, and I am new to rspec and TDD)
Thanks for your time and assistance. I truly appreciate your help and insights.
I believe the goal with this section was more around making the specs more readable and easier to follow.
One thing I noticed is that we are asserting things around “Invitation”, particularly the after_save callback, so the outermost before block that is updating team and team_owner are not needed - we can remove team_owner as well!
Therefore I think all we need is a build_invitation method that’s return an invitation record:
def build_invitation
user = User.new(email: "rookie@example.com")
team = Team.new(name: "A fine team")
Invitation.new(team: team, user: user)
end
By defining only the method above, and making the necessary adjustments, all the specs will pass
# spec/factories.rb
FactoryBot.define do
factory :invitation do
user
team
end
factory :user do
email { "john_doe@example.com" }
invited { false }
team
end
factory :team do
name { "Backend Devs" }
end
factory :magazine do
name { "The Ruby Times" }
end
factory :person do
end
factory :subscription do
person
magazine
end
end
# spec/support/helpers/invitation_helper.rb
module Helpers
module InvitationHelper
def build_invitation_with(team_name: nil, user_email: nil)
invitation = build(:invitation)
invitation.team = build(:team, name: team_name) if team_name
invitation.user = build(:user, email: user_email) if user_email
invitation
end
end
end
# spec/models/invitation_spec.rb
require "rails_helper"
RSpec.configure do |c|
c.include Helpers::InvitationHelper
end
RSpec.describe Invitation do
describe "callbacks" do
describe "after_save" do
context "with valid data" do
it "invites the user" do
new_user = create(:user)
invitation_for_new_user = build(:invitation, user: new_user, team: new_user.team)
invitation_for_new_user.save
expect(new_user).to be_invited
end
end
context "with invalid data" do
it "does not save the invitation" do
invalid_invitation = build(:invitation, team: nil)
invalid_invitation.save
expect(invalid_invitation).not_to be_valid
expect(invalid_invitation).to be_new_record
end
it "does not mark the user as invited" do
new_user = create(:user)
invalid_invitation_for_new_user = build(:invitation, user: new_user, team: nil)
invalid_invitation_for_new_user.save
expect(new_user).not_to be_invited
end
end
end
end
describe "#event_log_statement" do
context "when the record is saved" do
it "includes the name of the team" do
invitation = build_invitation_with(team_name: "A fine team")
log_statement = invitation.event_log_statement
expect(log_statement).to include("A fine team")
end
it "includes the email of the invitee" do
invitation = build_invitation_with(user_email: "rookie@example.com")
log_statement = invitation.event_log_statement
expect(log_statement).to include("rookie@example.com")
end
end
context "when the record is not saved but valid" do
it "includes the name of the team" do
invitation = build_invitation_with(team_name: "A fine team")
log_statement = invitation.event_log_statement
expect(log_statement).to include("A fine team")
end
it "includes the email of the invitee" do
invitation = build_invitation_with(user_email: "rookie@example.com")
log_statement = invitation.event_log_statement
expect(log_statement).to include("rookie@example.com")
end
it "includes the word 'PENDING'" do
invitation = build(:invitation)
log_statement = invitation.event_log_statement
expect(log_statement).to include("PENDING")
end
end
context "when the record is not saved and not valid" do
it "includes INVALID" do
invalid_invitation = build(:invitation, user: nil)
log_statement = invalid_invitation.event_log_statement
expect(log_statement).to include("INVALID")
end
end
end
end
Note: I also changed config.use_transactional_fixtures = false to config.use_transactional_fixtures = true in spec/rails_helper.rb file so that any data created within an example will be run within a transaction and rolled back.
Any advice for improvement would be appreciated. Thanks!