← Back to Upcase

Dealing with associations in specs

(André Ligné) #1

I’m having some questions how I should go about testing a model with many
relations. In the example below the player requires both a user and a
game to be valid.

describe Player do
  describe "#score" do
    it "increases goals_scored by one" do
      user = create :user
      game = create :game, score: 0
      player = create :player, user: user, game: game, goals_scored: 0
      
      player.score!
      
      expect(player.goals_scored).to eq 1
    end
  end
end

I feel that this pattern repeats itself all over my tests and was wondering
how I could go about testing, in this case, the score method without always
having to create a User and Game to go alongside it.

  • Should I just always create a User and Game in the factory?
  • Should I define them in a before block?
  • Should I extract the whole setup into a helper method?

I’d love to hear your suggestions! :smile:

bzf

(Andy Waite) #2

I find that although using let and before blocks often leads to less code, it can result in scenarios where it’s difficult to follow the flow of the test.

My preference is to keep the setup phase short and focused on the specific behaviour being tested, and have the incidental aspects handled by a helper method.

In your situation, I would probably extract a build_player method:

def build_player(goals_scored:)
  return @player if @player # memoize the result

  user = create :user
  game = create :game, score: 0
  @player = create :player, user: user, game: game, goals_scored: goals_scored
end

so the test then becomes:

    it "increases goals_scored by one" do
      player = build_player(goals_scored: 0)
      
      player.score!
      
      expect(player.goals_scored).to eq 1
    end

Notice that I’m passing goals_scored to the helper method. This means the test can be read on its and should be broadly understood without having to refer to the ‘implementation details’ in the helper method.

You can then reuse that helper method in other specs by adding additional keyword arguments with reasonable defaults. That means you only need to override the attributes that you care about for a given spec, e.g.

def build_player(user: create(:user), game: create(:game, score: 0), goals_scored: 7)
  @player ||= create(:player, user: user, game: game, goals_scored: goals_scored)
end

I’m always hesitant about creating associated records within the FactoryGirl definition, as it can lead to large graphs of objects being created unnecessarily, which slows down the tests and can be difficult to debug.

A couple of other things to note:

  • You can probably use FactoryGirls’ build method instead of create to avoid unnecessary database calls.
  • You might want to consider setting the score attribute of Game in the factory, since it’s not really relevant to the test itself.
1 Like