SoftwareManCal

Ruby/Rspec enthusiast who sometimes writes articles


Subject vs Let; Is there a difference?

This topic might seem straightforward at first glance—it just makes sense intuitively. But is there an actual difference? Let’s delve into the details and examine what is really happening behind the scenes.

require "rails_helper"

RSpec.describe User do
  describe "associations" do
    subject(:user) { create(:user) }

    it do
      expect(user).to have_many(:carts)
    end
  end
end
Ruby
RSpec.describe User do
  describe "associations" do
    let(:user) { create(:user) }

    it do
      expect(user).to have_many(:carts)
    end
  end
end
Ruby

These two approaches are essentially the same. On the backend, we create the user for the test, establish its associations, run the test, and then roll back the database.

There’s an obvious advantage when it comes to subjects: we get the Shoulda matchers, which come with their own pros and cons. Understanding when to use a subject becomes more crucial due to the ambiguity surrounding it, especially in terms of where it’s built and the contexts in which you’re using it. RuboCop enforces naming your subjects, which is something I’m uncertain about. Depending on how your test is set up, the subject should be evident.

One notable difference when discussing subjects and lets in the context of associations and Shoulda matchers is that defining the subject might actually create the object rather than just building it internally. Let’s compare the two examples before and after to illustrate this.

RSpec.describe User do
  subject(:user) { create(:user) }

  it { is_expected.to have_many(:carts) }
end
Ruby
RSpec.describe User do
  let(:user) { create(:user) }

  it { is_expected.to have_many(:carts) }
end
Ruby

At first glance, these two approaches should essentially achieve the same result. Both pass their tests without any issues. However, the key difference is that the subject will create the user for the test, whereas the let variable, if never called, means RSpec will use the described class, User, without formally creating it.

This seemingly small difference can significantly impact the speed of tests when examined more closely. Considering how extensive a user spec file can be, with all its associations and tests, using subject might actually cause a major slowdown.

# subject definition
Finished in 0.23173 seconds (files took 9.77 seconds to load)
1 example, 0 failures

#vs the let definiton
Finished in 0.15198 seconds (files took 10.29 seconds to load)
1 example, 0 failures
Ruby

So even though there’s no technical difference in calling a subject vs. a let variable, it does make a slight difference when we use Shoulda matchers and when we decide to create objects. Even for that one test, we should expect a significant improvement in test performance if we stop using subject for tests that don’t actually require objects to be built.



Leave a Reply

Your email address will not be published. Required fields are marked *