Hmm... That title looks awfully unimpressive: it. However, that simplicity is one of the beauties of Ruby: how the language let you write such cool stuff like:
it "should do blah blah blah" do
do_it
check_it
end
So, without further ado, let's jump in.
Looking at the rspec rdocs (making sure that I'm looking at the 1.1.1 docs), it creates a Spec::Example::Example and adds it to the current example group (the one creates by the describe method). Here's the code:
# File lib/spec/example/example_group_methods.rb, line 97
97: def it(description=nil, &implementation)
98: e = new(description, &implementation)
99: example_objects << e
100: e
101: end
That new call is interesting to me. We are currently in example_group_methods inside of an ExampleGroup. If we look at initialize on ExampleGroup, it just sets the @_defined_description and @_implementation.
In SharedExampleGroup, it also calls describe and the like.
I just noticed this:
class SharedExampleGroup < Module
class ExampleGroup
Why is SharedExampleGroup < Module? I guess I assumed that SharedExampleGroup < ExampleGroup, but that isn't the case. I can see that Ruby doesn't enforce any sort of type hierarchy, since the types are so amorphous, but it makes sense from a design perspective. Unless, of course, I'm completely misunderstanding the relationship.
[NOTE: I posted a message to the rspec-users on this (I probably should have sent it to rspec-dev). I will post any information I found out as an update to this entry.]
In any case, the it method creates a new example group, adds it to the example_objects array (why isn't it @example_objects?). Oh, I see:
def example_objects
@example_objects ||= []
end
Interesting. Lazy initialization of an instance variable. I will have to keep this pattern in mind, especially as I continue to look through the code here. For those who don't know, the ||= operator is shorthand for a = a || b. So, if a is nil, then the value of b is used, otherwise a is returned. Couple that with the fact that the return value of an assignment expression is the value assigned, we get a great pattern:
def return_and_assign
a ||= b
end
If a is not nil, then it is assigned to itself and returned. If a is nil, then it gets the value of b and is returned. Cool. Put this into a method to retrieve an instance variable and you get lazy initialization.
In any case, this ends the overview of it. Basically, it creates a new Example, adds it to the example_objects instance variable and then returns the just created Example.
This ends the initial setup of your specs, getting ready for a runner to run them. Because I am impatient, we are going to skip the code for the runner and start investigating what happens in the code block that you pass to it: that is, your actual spec. This will lead us into matchers, which is where the action is.