A better before block setup in RSpec?

So I’m writing a before do block for an rspec test suite. Essentially, I just need to seed the database with kite and tail objects (where a Kite has_many Tail(s)). I never need to directly access the kite or tail objects except for setup purposes in the before do block.

Is Approach 2 the ideal setup (or at least a better setup than Approach 1) since I’m no longer creating the extraneous arrays? Is there a more ideal setup? Interested in a better way to do pre-test object creation in the database.

Approach 1 (could definitely use improvement)

  before do
    @user = create(:user, first_name: "Jen", last_name: "Smith")
    @kites = []
    @tails = []
    (0..2).each { |i| @kites << create(:kite) }
    (0..2).each { |i| @tails[i] = create(:tail, tail_length:  (i+1)+100.0), kite: @kites[i], user: @user) }
  end

Approach 2

  before do
    @user = create(:user, first_name: "Jen", last_name: "Smith")
    0.upto(2) do |i|
      kite = create(:kite)
      create(:tail, tail_length: (i+1)+100.0), kite: kite, user: @user)
    end
  end

I prefer option 2. There are a few ways you can improve it:

  • If the @user variable isn’t used outside of the before block then you can use a local variable instead.
  • If the user’s name isn’t important for the test, consider setting first_name and last_name in the factory instead. And similarly with tail_length.
  • If you define a kite association in the tail factory, it will be created automatically. (Sometimes this feature is overused and make tests hard to understand, so use your judgement.)
  • You could use create_list and create_pair as a shorthand for creating multiple records.

So you could potentially reduce the whole block to something like:

before do
  user = create(:user)
  create_pair(:tail, user: user)
end
1 Like

Thank you @andyw8, all great points. Are you suggesting to use a local var for @user instead because the garbage collector cleanup of user (vs @user) would happen sooner?

Nothing to do with garbage collection, but as with a normal Ruby method, it’s better to limit the scope of a variable to the area of the code where it’s used.

There’s also the advantage that if you typo the name, attempting to use an undefined local variable will raise an error, but using an undefined instance variable will return nil.

2 Likes