Implicit subject should be memoized with let#768
Conversation
There was a problem hiding this comment.
That's fine if there's an ordering dependency, but this can just go right before Subject::ExampleMethods within the group of include statements rather than making it the very first thing and separating it from the other includes.
There was a problem hiding this comment.
Unfortunately it depends on Subject::ExampleGroupMethods, not Subject::ExampleMethods, which is in the extended modules, before all the includes.
There was a problem hiding this comment.
Got it - misread the comment :)
|
This is fantastic, @exviva! Are you planning to tackle the |
|
@myronmarston I'm struggling with the |
Thanks, I'll take a look. I don't have the time right now but hope to get to it this weekend. And yes, I consider these 2 separate issues that can be merged separately. I'd like to figure out what we're doing about the docs at first, though. |
|
@myronmarston I've combined the 2 modules, please have a look. One side effect of this is that |
There was a problem hiding this comment.
I don't think we need to put these methods into this module...they can just exist as direct singleton methods on the MemoizedHelpers module. Singleton methods don't get added to the host class when the module is mixed in so there's no danger of "leaking" these methods into the user's namespace.
Plus, I think it's kinda confusing that the code below looks for a module named LetDefinition and this one is also called LetDefinitions, but the module it is looking for is not this module....it's example_group::LetDefinitions.
Actually, I noticed that Alternately, |
|
Ok, I think I'm getting somewhere. Have a look at these examples: describe 'Implicit subject instantiates the most top-level class used in `describe`' do
# it { should be_an_instance_of(String) }
its(:class) { should eq(String) }
describe OuterClass do
it { should be_an_instance_of(OuterClass) }
its(:class) { should eq(OuterClass) }
describe 'another string' do
it { should be_an_instance_of(OuterClass) }
its(:class) { should eq(OuterClass) }
describe InnerClass do
it { should be_an_instance_of(OuterClass) }
its(:class) { should eq(OuterClass) }
end
end
end
endThey all pass on v2.12.2, but only until I...uncomment the first My proposed solution is to mix in the implicit subject only if the described class is a class or module. Otherwise there should be no implicit subject. This way you can nest groups multiple times, mixing strings and classes/modules as descriptions, and the implicit subject will always instantiate the closest Class (or return the closest Module), which seems most intuitive. Another idea I see is to mix in the implicit subject only in the top-level example group, but this doesn't feel right if people want to nest The problem with |
|
@exviva -- I started looking into the |
|
I also cherry-picked over your |
|
Cool. I finally understood, why the examples with describe 'using its with before and let blocks' do
subject { :symbol } # or whatever
let(:subject_id_in_let) { subject.object_id }
before { @subject_id_in_before = subject.object_id }
its(:object_id) { should eq(subject_id_in_let) }
its(:object_id) { should eq(@subject_id_in_before) }
endIt's because when describe 'using its with before and let blocks' do
subject { :symbol }
let(:subject_id_in_let) { subject.object_id } # easy to think it'd always return :symbol.object_id...
describe(:object_id) do
subject { :symbol.object_id }
it { should eq(subject_id_in_let) } # but here it actually returns :symbol.object_id.object_id
end
endIn other words, doh...:). Actually, I'd call it a bug, because I'd still expect I'll try master on my project on Monday, and come back if I find any more issues. I've had some weird behaviour of |
|
Nice work getting to the bottom of this. That's very, very subtle, and is another argument for using
Yep, I was playing with the example you pasted above, too, and there is something weird going on. I'm going to play with it more to get to the bottom of it. And actually, in your example above, here's how I actually think it should behave: describe 'Implicit subject instantiates the most top-level class used in `describe`' do
# it { should be_an_instance_of(String) }
its(:class) { should eq(String) }
describe OuterClass do
it { should be_an_instance_of(OuterClass) }
its(:class) { should eq(OuterClass) }
describe 'another string' do
it { should be_an_instance_of(OuterClass) }
its(:class) { should eq(OuterClass) }
describe InnerClass do
it { should be_an_instance_of(InnerClass) }
its(:class) { should eq(InnerClass) }
end
end
end
endIn other words, I think the implicit subject should be an instance of the inner-most described class, or if, there is not described class, the outermost described thing. @dchelimsky -- any thoughts on what the correct thing is when nesting a |
|
@myronmarston nice work with I've just used 538285b in my project, and unfortunately it still has a regression in describe Coupon do
describe 'delegating name to source' do
let(:name) { 'Best program ever!' }
before { subject.source = LoyaltyProgram.new(name: name) }
its(:name) { should eq(name) }
end
endFailure is I'll investigate it a bit today. |
|
@myronmarston here's a solution I came up with to keep If you like it, I'll cover it with proper specs and docs. |
|
@exviva -- it's hard to say whether or not I like that solution because it's not clear to me what the bug is. Can you come up with an rspec-core commit that adds a failing example for your use case? |
|
Ah, sorry. The bug is that MyClass = Struct.new(:some_attr)
describe MyClass do
before { subject.some_attr = :foo } # [1]
its(:some_attr) { should eq(:foo) }
endOn line [1], I'll write a failing example in a moment. |
|
Ah, I see what the problem is now...in 2.12.2 (and before), |
|
@myronmarston I don't think this will solve the problem, at least not for We can either have |
Hmm, you're right. That said, I believe that has been the behavior of
The latter approach appeals to me because I'd prefer not to introduce another method (for reasons we've discussed previously). However, I'm not convinced that it'll solve the problem; changing what |
|
Including a module does the trick: https://github.com/exviva/rspec-core/compare/its_subject. But there is no way of solving this without adding a new method to the example (with And since I'd expect Have a look at my branch, let me know if it makes sense, if yes, I'll add some more docs and open a PR. |
|
I like that solution a lot. My one suggestion is to see if we can make the specs for that more clear...they're a bit convoluted. Please open a PR for it! |
This is a partial solution to #766, the only thing left is the problem with
its.I'm open to feedback and suggestions on improving this solution.