Allow let! without a block
Created by: danielfrey
We make extensive use of rspec tests in our projects. The biggest has over 5200 tests at the moment. Most of the tests need a small or normal setup, but some need a really heavy setup. With heavy setups we usually build up a tree of dependent objects. A big part of the setup-tree is built in the most outer context, whereas specific object are only built in the inner contexts. Since those tests are often integration tests, these objects are activerecord objects created with factory-girl.
Before "let" was introduced, we did all setups in the before-hooks. With the introduction of "let" things got clearer as we all know. Typically not every test needs the full setup, but for understanding the object dependencies, it's better to define most of it in the most outer context and then activate the objects by calling the variable lazy loading the block. But, and here comes the problem, if you need in some tests a "let!" to e.g. have a saved version of an object, then you have two options to do this, both not really satisfying. Either you define this object with "let!" in the topmost context an therefore instatiate it in every test and not only in the needed ones. Or you define it with "let", but then you have to call the variable explicitly in the test or in a before-hook, if you need it in this context.
As a possible and in my eyes clean solution for this problem would be, of you could define an object with "let" and, in a second statement, e.g in the inner context just declare it a second time with "let!", but without declaring the block.
With this solution you can build the heavy setup logically in the most outer context (or wherever needed) and in the inner context you just call "let!" without a block. This let's your tests become much more readable.
You could of course argue that you should build smaller setups, eg with more complex factories. But in really big applications large setups are reality. And the proposed let! would be a really elegant solution.
As a (admittedly very constructed) sample look at this:
describe Family do
context "with big setup" do
let!(:father) {FactoryGirl.create :person, gender: :male}
let!(:mother) {FactoryGirl.create :persongender: :female}
let(:parents) {FactoryGirl.create :parents, father: father, mother: mother}
let(:son) {FactoryGirl.create :person, gender: :male, parents: parents}
let(:daughter) {FactoryGirl.create :person, gender: :female, parents: parents}
let(:family) {FactoryGirl.create :family, with: [parents, son, daughter]}
context "without children" do
it "test1" do
Person.where{gender == 'male'}.should eq(1)
end
end
# the "old" way #1
context "with children" do
it "test2" do
son
daughter
Person.count.should eq(4)
end
end
# the "old" way #2
context "with children" do
before do
son
daughter
end
it "test2" do
Person.count.should eq(4)
end
end
# the "proposed new" way
context "with children" do
let!(:son) #only instantiated in this context
let!(:daughter) #only instantiated in this context
it "test2" do
Person.count.should eq(4)
end
end
end
end
What do you think?