Suppose you have an app where users can add each other to their friendlist. The corresponding code for the model would be:
models/user.rb
class User < ApplicationRecord
has_many :friendships
has_many :friends, through: :friendships
has_many :inverse_friendships, class_name: "Friendship", foreign_key: "friend_id"
has_many :inverse_friends, through: :inverse_friendships, source: :user
def has_friend?(user)
friends.where(id: user.id).any? || inverse_friends.where(id: user.id).any?
end
end
models/friendship.rb
class Friendship < ApplicationRecord
belongs_to :user
belongs_to :friend, class_name: "User"
end
Now you want to test the has_friend? method with RSpec, FactoryGirl and Faker. You create factories:
spec/support/factories.rb
FactoryGirl.define do
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
password "12345"
end
factory :friendship do
user
association :friend, factory: :user
end
end
Then you write your tests:
spec/models/user_spec.rb
require "rails_helper"
RSpec.describe User do
context "instance methods" do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(:friendship) { create(:friendship, user: user1, friend: user2) }
specify ".has_friend?" do
expect(user2.has_friend?(user1)).to be_truthy
expect(user1.has_friend?(user2)).to be_truthy
end
end
end
You run your tests... and see the red error message. The has_friend? method always returns false, though when setting a breakpoint right before your expectations (for example, using the Pry gem) you do see that the friendship was established correctly. So, what's up?
The problem is with the let(:friendship) { create(:friendship, user: user1, friend: user2) } line. The let method is somewhat lazy and will not actually do anything until the object is being called anywhere in your specs. As long as the friendship is not used anywhere in our tests, it is not being set and therefore user1.has_friend?(user2) will always return false.
The solution is really simple - use let! instead of let if you want to enforce the creation of an object. Therefore the code should be:
spec/models/user_spec.rb
require "rails_helper"
RSpec.describe User do
context "instance methods" do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let!(:friendship) { create(:friendship, user: user1, friend: user2) } # <===
specify ".has_friend?" do
expect(user2.has_friend?(user1)).to be_truthy
expect(user1.has_friend?(user2)).to be_truthy
end
end
end
Now the friendship will be created and your test will turn green!