Skip to content
This repository

yield current running example to it/example and before/after hooks #666

Merged
merged 7 commits into from 10 months ago

8 participants

David Chelimsky Don't Add Me To Your Organization a.k.a The Travis Bot Myron Marston Sam Goldman Pat Maddox Jon Rowe Coveralls Andy Lindeman
David Chelimsky
Owner

Let's yield current running example to it/example and before/after hooks instead of exposing it via the implicit example getter.

@myronmarston, @alindeman comments, please

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 478a8e4 into 4f101b3).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 0861172 into 4f101b3).

Myron Marston
Owner

Interesting idea. I assume this is in response to #663?

I like the fact that this creates fewer potential naming conflicts (especially since example could reasonably be used as a method name by end users), but I do think this potentially makes things more cumbersome. Consider some code like this:

module SomeHelperMethods
  def some_helper_method
    if example.metadata[:blah]
      do_something
    end
  end
end

RSpec.configure do |c|
  c.include SomeHelperMethods
end

In this highly contrived and totally generic example, the helper method is able to directly access the example metadata in a conditional. With your proposed change, this would no longer be possible -- all helper methods that need access to the example would have to accept (and be passed) an example argument. That feels more cumbersome than the current way that it works -- particularly when you have a helper method that calls a helper method that calls a helper method that needs access to the example. All the intermediary helper methods would need to accept (and pass on) the example arg, and then the example itself would have to pass it in.

So I guess you could say I'm on the fence about this.

BTW, if we do make this change, how many other method names are we "squatting" (for lack of a better term)? Are there likely to be other methods that could have similar conflicts?

David Chelimsky
Owner

What if we hung example on RSpec? e.g.

module SomeHelperMethods
  def some_helper_method
    if RSpec.example.metadata[:blah]
      do_something
    end
  end
end

RSpec.configure do |c|
  c.include SomeHelperMethods
end

That makes it available for the edge case. Another option would be to offer a config option:

RSpec.configure do |c|
  c.expose_current_running_example_as :rspec_example
end

or some such.

The only other instance methods on ExampleGroup are running_example (deprecated predecessor to example), described_class (which, arguably, should be treated the same way) and instance_eval_with_rescue, which would be easy enough to refactor out (only used internally).

David Chelimsky
Owner

Doing this w/ described_class, btw, would hurt :)

David Chelimsky
Owner

What if we don't deprecate these methods for rspec-2.x, but "soft deprecate" them (i.e. a deprecation notice tells users how to turn it off - requires some infrastructure).

BTW - this is something I've wanted for a long time. I actually did it in response to a reaction I had working on slides for a preso I'm doing next week. This happened a lot w/ The RSpec Book - I'm writing about something and think "wow, that's lame," and fix it :) Perhaps it was subconsciously on my mind due to #633 as well, but that's not what I was thinking of.

In regard to #633, however, part of this commit points at the solution if we don't expose the block arg or deprecate example: always reference an ivar internally instead of accessing the example through the method, or hand the method internally on RSpec. WDYT about doing that regardless?

Myron Marston
Owner

What if we hung example on RSpec?

I'm OK with that idea, although I think it should be current_example, because that's a lot more self-descriptive.

I also realized there's a fairly trivial way for end-users to add an example helper method to there example groups if they need it:

module ExampleHelper
  extend RSpec::Core::SharedContext
  attr_reader :example
  before { |ex| @example = ex }
end

RSpec.configure do |c|
  c.include ExampleHelper
end

Given the simplicity of that, maybe we should just direct users who need this towards doing that? It feels like overkill to add a bunch of infrastructure for a fairly simple deprecation like this.

Anyhow, one other thing I thought of...I think this is a potentially breaking change for anyone who uses lambdas for hooks or examples, e.g.:

def before_hook_for_foo(some_arg)
  lambda do
    # do something
  end
end

before &before_hook_for_foo(:bar)

Lambda semantics dictate that it raises an error if the number of args don't match (in contrast, proc semantics -- which are used for blocks -- do not raise an error if the number of args don't match). With the new example arg passed to the lambda, it'll blow up, I think.

In regard to #633, however, part of this commit points at the solution if we don't expose the block arg or deprecate example: always reference an ivar internally instead of accessing the example through the method, or hand the method internally on RSpec. WDYT about doing that regardless?

I think that's a good idea, regardless.

Sam Goldman

I do use example in the manner described by @myronmarston, but only ever to get at the current example's metadata. I wonder if there isn't some nicer way to expose metadata

Pat Maddox

I personally like the block syntax & explicitly passing the example to the helper. Perhaps it's verbose because all helper methods will need it, but I like that the example is an explicit argument. It means the helper has a bit less knowledge of RSpec's internals. Granted you know it's in a module that's mixed into RSpec so the whole point is to extend it...but I figure anything we can do to have an explicit API is good.

Jon Rowe
Collaborator

What @totallymike said... /cc @myronmarston

Myron Marston
Owner

I need to think about it some more, but I'd say the earliest we would make this change is 3.0.

Myron Marston
Owner

This has sat for a while but I still like it and would like to get it in RSpec 3. FWIW :).

David Chelimsky
Owner

It needs to be rebased. Working on that now.

Coveralls

Coverage Status

Coverage increased (+0%) when pulling 45d0e2b on yield-example into dee12fc on master.

David Chelimsky
Owner

I rebased this off master, force pushed, and added another commit. I'm pretty happy with it as/is but welcome feedback /cc @myronmarston @alindeman @JonRowe @soulcutter @samphippen

Coveralls

Coverage Status

Coverage increased (+0%) when pulling 7e0a9c3 on yield-example into dee12fc on master.

lib/rspec/core/example_group.rb
@@ -67,10 +68,22 @@ def self.define_example_method(name, extra_options={})
67 68
         end
68 69
 
69 70
         # Defines an example within a group.
  71
+        # @example
  72
+        #   example do
  73
+        #   end
  74
+        #
  75
+        #   example "does something" do
  76
+        #   end
  77
+        #
  78
+        #   example "does something", :with => 'addtional metadata' do
  79
+        #   end
  80
+        #
  81
+        #   example "does something" do |ex|
  82
+        #     # ex is a wrapper for the current running example
2
Myron Marston Owner

I thought ex was the current example object? In what sense is it a wrapper of the current example rather than than the current example itself?

David Chelimsky Owner
dchelimsky added a note June 19, 2013

You're correct. I think at one point I had a wrapper in mind (a la example and group proxies for rspec-1 formatters), but ended up just with the example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rspec/core/example_group.rb
@@ -67,10 +68,22 @@ def self.define_example_method(name, extra_options={})
67 68
         end
68 69
 
69 70
         # Defines an example within a group.
  71
+        # @example
  72
+        #   example do
  73
+        #   end
  74
+        #
  75
+        #   example "does something" do
  76
+        #   end
  77
+        #
  78
+        #   example "does something", :with => 'addtional metadata' do
1
Myron Marston Owner

s/addtional/additional/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston myronmarston commented on the diff June 19, 2013
lib/rspec/core/example_group.rb
((5 lines not shown))
443  
-      # Returns the {Example} object that wraps this instance of
444  
-      # `ExampleGroup`
445  
-      attr_accessor :example
  461
+      def initialize
  462
+        @_current_rspec_example = nil
  463
+      end
  464
+
  465
+      def example=(current_example)
  466
+        @_current_rspec_example = current_example
  467
+      end
  468
+
  469
+      # @deprecated use a block argument
  470
+      def example
  471
+        RSpec.deprecate("example", :replacement => "a block argument")
  472
+        @_current_rspec_example
  473
+      end
11
Myron Marston Owner

I was originally thinking we'd remove this method in 3.0 and than add a deprecation like this to 2.99. I'm open to keeping it deprecated in 3.0, though, especially if you think it's important. Generally, for most breaking changes like this I'm in favor of deprecating in 2.99 and removing in 3.0, except in a few circumstances:

  • It could trip up newcomers who are using an old tutorial
  • The API is likely to be used by an extension gem, which the user may not have direct control over and can't easily update during the 2.x -> 2.99 -> 3.0 migration process.

Do you think either of these apply here?

David Chelimsky Owner
dchelimsky added a note June 19, 2013

I agree that's the right path - just wasn't thinking clearly that master is 3.0. So let's merge this (when we do) as/is to both 2-14-maintenance and master and I'll add another commit to master to remove the deprecated methods. Fair?

Myron Marston Owner

Sounds fair...although we need this in 2-99-maintenance, not 2-14-maintenance. Unless you want this to go out in 2.14? I think just being deprecated in 2.99 is sufficient, given that it's not very common to access the example.

Myron Marston Owner

So if we're going to remove the #example and #running_example, is there a way we can remove @_current_rspec_example as well? Or do we still need the instance variable?

David Chelimsky Owner
dchelimsky added a note June 19, 2013

We still need to access it internally - just not expose it via a method. The assumption here is that it won't conflict with a @_current_rspec_example instance var in your app's examples :)

David Chelimsky Owner
dchelimsky added a note June 19, 2013

re: 2.14 v 2.99 v 3.0. We can definitely do 2.99 (w/ deprecations) and 3.0 (with removal), but it feels odd to add a new feature to 2.99 and only give people 2.99 on to use it. WDYT?

Myron Marston Owner

We still need to access it internally - just not expose it via a method. The assumption here is that it won't conflict with a @_current_rspec_example instance var in your app's examples :)

Random idea: what if we stored the current example in a thread local? That would remove the need for the ivar. If/when we ever wanted to make a multi-threaded runner, that would be automatically thread safe in a way that instance variables aren't. We could then expose it from RSpec.current_example (which I think was one of your earlier ideas), which can be handy for when a helper method needs to access the example metadata, and we could remove the example= method in this class. I don't know how likely it is that users would have an example= helper method, but it's not outside the realm of possibility.

re: 2.14 v 2.99 v 3.0. We can definitely do 2.99 (w/ deprecations) and 3.0 (with removal), but it feels odd to add a new feature to 2.99 and only give people 2.99 on to use it. WDYT?

I don't consider this a new feature. I consider it an API change. The current example has always been available; this just changes the means users use to access it.

David Chelimsky Owner
dchelimsky added a note June 19, 2013

What's your concern about an instance variable here? It's got a name that is very unlikely to cause a conflict. We can always move to a thread local when we want to make a multi-threaded runner, which will likely need other changes as well.

I agree re: API change v new feature. 2.99 and 3.0 it is.

Myron Marston Owner

What's your concern about an instance variable here? It's got a name that is very unlikely to cause a conflict. We can always move to a thread local when we want to make a multi-threaded runner, which will likely need other changes as well.

I don't have a specific concern. It's just the anal part of me thinking about the fact that the goal here was to stop /reduce polluting the user's namespace, and we're still doing so -- just with something that's less likely to conflict.

David Chelimsky Owner
dchelimsky added a note June 19, 2013

I think thread local is overkill for this, and if we expose RSpec.current_example it will get used. I'd recommend keeping the ivar for now. Can always adjust later if a holistic approach emerges.

Myron Marston Owner

I'm fine with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston myronmarston commented on the diff June 19, 2013
lib/rspec/core/example_group.rb
@@ -439,16 +458,24 @@ def self.set_ivars(instance, ivars)
439 458
         ivars.each {|name, value| instance.instance_variable_set(name, value)}
440 459
       end
441 460
 
442  
-      # @attr_reader
443  
-      # Returns the {Example} object that wraps this instance of
444  
-      # `ExampleGroup`
445  
-      attr_accessor :example
  461
+      def initialize
  462
+        @_current_rspec_example = nil
  463
+      end
  464
+
  465
+      def example=(current_example)
  466
+        @_current_rspec_example = current_example
  467
+      end
3
Myron Marston Owner

Is this being called anywhere? I've scrolled down this diff 3 times looking for where it is being called but can't find it. I figure it must be called somewhere given that other things are relying on @_current_rspec_example...

David Chelimsky Owner
dchelimsky added a note June 19, 2013

Example#run

Myron Marston Owner

Thanks, I missed the fact that example= already existed via the attr_accessor declaration. I was thinking this was a new method and didn't see anything in the diff that was calling it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston myronmarston commented on the diff June 19, 2013
lib/rspec/core/example_group.rb
((9 lines not shown))
474 501
         rescue Exception => e
475  
-          raise unless example
476  
-          example.set_exception(e, context)
  502
+          raise unless @_current_rspec_example
  503
+          @_current_rspec_example.set_exception(e, context)
2
Myron Marston Owner

Given that this method accepts an example argument, it would seem cleaner to use that rather than the instance variable. Any reason to prefer the instance variable over the local?

David Chelimsky Owner
dchelimsky added a note June 19, 2013

I changed it and got an error. Looks like this is a hack to ensure different behavior for an after(:all) block. Looking into a cleaner, more expressive fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston myronmarston commented on the diff June 19, 2013
lib/rspec/core/pending.rb
@@ -76,7 +76,7 @@ def pending_fixed?; true; end
76 76
       #         # ...
77 77
       #       end
78 78
       def pending(*args)
79  
-        return self.class.before(:each) { pending(*args) } unless example
  79
+        return self.class.before(:each) { pending(*args) } unless @_current_rspec_example
3
Myron Marston Owner

This has nothing to do with your PR since it was already this way...but under which circumstances would this be called w/o a current example? I can't think of a case where this early return would actually happen.

David Chelimsky Owner
dchelimsky added a note June 19, 2013

It's for the before(:all) { pending() } case. Happy to consider eliminating support for that, but it works today.

Myron Marston Owner

Gotcha. It's fine as is (a refactoring to that would really be a separate issue). Mostly I was just trying to understand what this case was here for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston myronmarston commented on the diff June 19, 2013
lib/rspec/core/example_group.rb
((24 lines not shown))
448 476
       def running_example
449  
-        RSpec.deprecate("running_example",
450  
-                        :replacement => "example")
451  
-        example
  477
+        RSpec.deprecate("running_example", :replacement => "a block argument")
  478
+        @_current_rspec_example
452 479
       end
3
Myron Marston Owner

I noticed there's no spec (that I can find, anyway) for either #example or #running_example, to show that it continues to return the example object and that they print a deprecation warning.

Do you think that's worth adding?

David Chelimsky Owner
dchelimsky added a note June 19, 2013

Added in ed5a3be

Myron Marston Owner

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Myron Marston
Owner

Looking good. There are probably some cukes that we should edit so as to use this new style as well.

Coveralls

Coverage Status

Coverage increased (+0%) when pulling 6e02be3 on yield-example into dee12fc on master.

Myron Marston
Owner

Do you think that we should yield the example from let and subject blocks as well?

I've seen let used like so:

shared_context "user helpers" do
  let(:user) { User.find(example.metadata.fetch(:user_id)) }
end

describe "Accessing the API as an admin", user_id: 123 do
  include_context "user helpers"
end

...and for this to continue to be possible the example needs to be yielded to let, I think:

shared_context "user helpers" do
  let(:user) { |ex| User.find(ex.metadata.fetch(:user_id)) }
end
David Chelimsky
Owner

Do you think that we should yield the example from let and subject blocks as well?

Yes. I hate to think of it being used :), but at least it's consistent. Will add.

Myron Marston
Owner

Yes. I hate to think of it being used :), but at least it's consistent. Will add.

I agree that it could easily be mis-used and get out of hand, but I don't think it's too bad if used sparingly. But yeah -- consistency is my main reason for asking here.

David Chelimsky
Owner

Do you think that we should yield the example from let and subject blocks as well?

Yes. I hate to think of it being used :), but at least it's consistent. Will add.

Not so fast. The support for using super complicates this a bit. Because the block passed to let is now passed to define_method, adding block args to the block effectively defines parameters on the resulting method. Not sure there's a way to support exposing the example to a let or subject block and super, return etc at the same time . WDYT?

Myron Marston
Owner

Not so fast. The support for using super complicates this a bit. Because the block passed to let is now passed to define_method, adding block args to the block effectively defines parameters on the resulting method. Not sure there's a way to support exposing the example to a let or subject block and super, return etc at the same time . WDYT?

It feels a bit like a hack, but you could do this, I think:

        def let(name, &block)
          # We have to pass the block directly to `define_method` to
          # allow it to use method constructs like `super` and `return`.
          raise "#let or #subject called without a block" if block.nil?
          MemoizedHelpers.module_for(self).send(:define_method, name, &block)

          # Apply the memoization. The method has been defined in an ancestor
          # module so we can use `super` here to get the value.
          define_method(name) do
            __memoized.fetch(name) do |k|
              arity = self.class.superclass.instance_method(name).arity
              args = arity.zero? ? [] : [@_current_rspec_example]
              __memoized[k] = super(*args, &nil)
            end
          end
        end

Basically, check the arity of the superclass method.

Coveralls

Coverage Status

Coverage decreased (-0%) when pulling 4f3f66a on yield-example into dee12fc on master.

David Chelimsky
Owner

OK - I added support for yielding the example to let and subject. It does check the arity, but directly on the block. This avoids some errors I saw when checking the superclass, and it also feels cleaner since we have access to the block directly. Let me know what you think.

Myron Marston
Owner

OK - I added support for yielding the example to let and subject. It does check the arity, but directly on the block. This avoids some errors I saw when checking the superclass, and it also feels cleaner since we have access to the block directly. Let me know what you think.

:+1: Much better than my solution! It also potentially performs better; rather than checking the conditional for each example that calls the let-defined method, it runs the check only once, when the let is defined.

Myron Marston myronmarston merged commit de7623e into from June 25, 2013
Myron Marston myronmarston closed this June 25, 2013
Myron Marston myronmarston deleted the branch June 25, 2013
Myron Marston
Owner

Thanks, @dchelimsky! Sorry it took me a while to get around to merging this.

As far as next steps goes, does this sound right?

  • Merge this into 2-99-maintenance as well.
  • Remove the deprecated methods from master.
  • Update the metadata cukes as needed since they reference the deprecated example method.

Let me know if you want help with any of that.

David Chelimsky
Owner

Done, done, and done. I updated the cukes in both master and 2-99-maintenance. Let me know if there are any questions.

Myron Marston
Owner

:+1: Thanks!

Myron Marston myronmarston referenced this pull request in rspec/rspec-expectations June 27, 2013
Merged

Access running example without `example` #275

Andy Lindeman
Owner

Hi all. Sorry I didn't get to review this sooner. Unfortunately this blew up the rspec-rails build on all branches, including 2-14-maintenance.

With these changes, what's the best way to get the current running example in a method that's not necessarily a hook? For instance rspec-rails' view specs determine what to render based on metadata

@dchelimsky, @myronmarston

Andy Lindeman
Owner

@JonRowe helped me remember that even the 2-14-maintenance build is pulling in the master branch of the other rspec-* gems. I'll fix that shortly.

Even so, what's the best way to handle this in master/3.0?

Myron Marston
Owner
Andy Lindeman
Owner
  • Maybe the 2-14-maintenance branch should build using the 2-14-maintenance branches of the other rspec repos? This should still work in 2.14.

Yep, you're right. I've now fixed up 2-14 and 2-99 by pulling in the same branches of rspec-core.

Andy Lindeman alindeman deleted the branch June 30, 2013
Myron Marston
Owner

The simplest change would probably to define a let like:

let(:example) { |ex| ex }

However, given that the point of this PR was to stop squating on example (in order to let users define methods name example if they wish), you may want to do this instead:

let(:_default_file_to_render) do |example|
  example.example_group.top_level_description
end

Either way, you may need to refactor rspec-rails' modules to use rspec-core's shared_context mechanism, so that you can put that stuff in a context that supports let.

Myron Marston
Owner

On a side note, I just realized that the fact that subject and let yield example helps make those constructs' relationship with examples more clear and helps show why it's nonsensical to access those constructs from before(:all). That's a nice side benefit :).

David Chelimsky
Owner
let(:_default_file_to_render) do |example|
  example.example_group.top_level_description
end

:heart::heart::heart:

Andy Lindeman
Owner

Perfect, thank you :)

Andy Lindeman
Owner

OK, it looks like some libraries like capybara also use example in hooks. See capybara/rspec.rb.

What should we recommend to the capybara folks? Is it possible to be compatible with both RSpec 2 and 3 there?

Jon Rowe
Collaborator

I think a fix similar to yours would work no?

let(:example) {  |current_example| current_example ? current_example : super }

or

let(:_current_example) { |current_example| current_example ? current_example : example }
Andy Lindeman
Owner

Yielding the example is new in 2.14, right? How do we maintain compatibility with versions before that and RSpec 3 (where example is gone entirely)?

Jon Rowe
Collaborator

Yielding the example will be new in 3, I don't think it's in even 2.14.0rc1... Hence the conditional, I've just tried this on 2.14 and it's a bit more like this:

let(:current_example) do |*args|
  yielded_example, *rest = args
  yielded_example ? yielded_example : example
end
Andy Lindeman
Owner

Is that what we recommend library authors (like capybara) do? That's pretty rough :/ I'll sleep on this, but I think we need something better.

Jon Rowe
Collaborator

It's a work around they could use for supporting multiple major versions, I think this is a conflict they will have to decide how to resolve, there's a couple of strategies that I can see...

1) they can use ugly work arounds temporarily
2) they can pick different integration code depending on rspec versions
3) they can drop support for 2.x

the later would be best for us by encouraging adoption of 3.x ;)

Whilst we should be sympathetic and help, we shouldn't let 3rd party libraries drive our decisions either.

David Chelimsky
Owner

Yielding the example is new in 2.14, right? How do we maintain compatibility with versions before that and RSpec 3 (where example is gone entirely)?

How about an opt-in via config like config.expose_example_as_example or some such? That would at least offer end-users a way of using RSpec 3 with libs that haven't moved forward yet.

Whilst we should be sympathetic and help, we shouldn't let 3rd party libraries drive our decisions either.
3) they can drop support for 2.x
the later would be best for us by encouraging adoption of 3.x ;)

And it would do just as good a job of encouraging, if not forcing, people to stop using RSpec. It's one thing to nudge people forward, and another to cut them off at the knees. We have to allow for the fact that it will take some time for 3rd party libs to adjust, and do what we can to make this all seamless for end-users. In fact - I'm beginning to think we should deprecate example but leave it in RSpec-3.

Jon Rowe
Collaborator

I'm more than prepared to go and lend a hand to things like capybara to help them gain RSpec 3.x support, I think in most cases the changes won't be that extreme, there are probably going to be more gems out there version locked to 2.x with a ~> that will cause issues than soft loaded gems like Capybara.

2.99 should print deprecation warnings for this, and we are planning to have that around for a significant time to help people get ready for the change, so I don't feel it's necessary to leave things like this around deprecated... we can help people upgrade for the change, we can document things they can do and suggested solutions, I hardly think this will be cutting people off at the knees.

I do however apologise for the language in my comment, it was more humour than perhaps it was taken.

David Chelimsky
Owner

I'm more than prepared to go and lend a hand to things like capybara to help them gain RSpec 3.x support

That's great.

2.99 should print deprecation warnings for this, and we are planning to have that around for a significant time to help people get ready for the change

I think it's the combination of 2.99 and 3.0.0.beta/rc that needs to be around for a while before 3.0.0 final goes out so lib maintainers and end-users alike can see the deprecation warnings and validate that their resulting changes work with the new version. We've talked about this before but haven't really defined what "a significant time" means. I think we need some means of validating that enough people (definition TBD) are successfully using 3.0 pre-releases with a sufficiently wide variety of 3rd party gems (definition TBD) in play. Surveys? A user-sourced compatibility chart? An opt-in stats report that rspec-3.0.0 pre-releases can send to a server so we can collect data like gem versions, which we can use to publish a compatibility chart? Other ideas?

Andy Lindeman
Owner

What if we did expose RSpec.current_example as mention earlier? That way capybara and others could do something like:

current_example = RSpec.respond_to?(:current_example) ? RSpec.current_example : example

(the check could also be written in such a way that it'd only be performed once for performance reasons)

... and maintain compatibility? Or is there a compelling reason to not expose it like that?

Andy Lindeman
Owner

RSpec.current_example could be exposed publicly, but we wouldn't necessary document it anywhere other than the API docs. It'd be for library authors, and users writing examples in their apps would be encouraged to use the block argument.

Thoughts?

Myron Marston
Owner

FWIW, I think the best way to encourage adoption of 3.x is to make the upgrade as simple and painless as possible, both for users and library authors, so there's no reason not to upgrade. That's why we're putting significant effort into 2.99, so that users have a very clear upgrade path, with explicit instructions about what's changing and how it affects them, rather than just a changelog they have to read to figure out how it applies to them.

In fact - I'm beginning to think we should deprecate example but leave it in RSpec-3.

I'm :-1: on this idea, but certainly could be convinced otherwise once we get more community feedback. One of the main wins of this PR was reducing the amount of namespace squatting we're doing, and if we leave it in place deprecated, we lose that benefit until RSpec 4...which is a long time to wait. I'd first like to see if we can't ease the transition in other ways.

RSpec.current_example could be exposed publicly, but we wouldn't necessary document it anywhere other than the API docs. It'd be for library authors, and users writing examples in their apps would be encouraged to use the block argument.

Thoughts?

:+1: I like this.

David Chelimsky
Owner

RSpec.current_example could be exposed publicly, but we wouldn't necessary document it anywhere other than the API docs. It'd be for library authors, and users writing examples in their apps would be encouraged to use the block argument.

Thoughts?
I like this.

That's backwards to me. If a lib author is going to make a change, we want him/her to follow the path forward and use the yielded example object, not propagate the dependency on example or running_example methods. Exposing the example and running_example methods helps end-users who are using libs that haven't been upgraded yet. They'd still be encouraged (via docs) to use the block arg in their own spec suites.

Jon Rowe
Collaborator

Thought, could we deprecate the direct calls to example now before we put out 2.14? That'd give even more notice in this case? I'm with @dchelimsky on the exposing RSpec.current_example is a bit of a backwards step...

David Chelimsky
Owner

I'm with @dchelimsky on the exposing RSpec.current_example is a bit of a backwards step...

Except that's not what I intended :)

I was saying that RSpec.current_example would serve end users, not lib authors, which is the opposite of what @alindeman had proposed. I'm actually in favor of including it to support end-users and don't view it as a step back.

Andy Lindeman
Owner

I'm a tad confused, sorry. Why would end users want RSpec.current_example?

I'm nervous right now because rspec-rails' test suite on master is badly red due to capybara using example in a before and after hook. And I don't know how to fix it without making a breaking change that forces capybara to release a new version than only supports RSpec >= 2.14 and 3.x (where this change is merged) ... or necessitate adding some conditional logic against ::RSpec:Core::VERSION

... and this makes me suspect there are many other libraries that are now broken against RSpec master and will need to release a new major version if we don't provide some easier way to support both RSpec 2 and 3.

I agree that sometimes we need to break compatibility in a major version release, but I feel like we should be conservative about it and only do it when there's a really compelling reason and no great way to support a simple unifying API.

My thought was that RSpec.current_example could be that API. But maybe there's something better or something I'm missing?

In my opinion, even if you think this change is positive, it's simultaneously small yet far-reaching: I think we should add, document, and maybe help (via pull requests) gems that integrate with RSpec to support both 2.x and 3.x.

Thoughts?

David Chelimsky
Owner

I'm a tad confused, sorry. Why would end users want RSpec.current_example?

They wouldn't except to protect against lib authors who are slow to upgrade.

Today there is some set of end users (possibly a set of 0, but I doubt it) who use example in their apps. When they upgrade to 2.99 they'll get deprecation warnings, they'll be able to use the yielded example instead, and they'll be ready for 3.0. Hooray!

Similarly there is some set of lib authors who use example in their libs. For the ones who update their libs to support RSpec 3 (with or without our help) they'll do the same thing: upgrade to 2.99, see deprecation warnings and use the yielded example instead (or use let to define a method that yields the example as @myronmarston described as a potential solution for rspec-rails). Once they release those changes their users can upgrade to RSpec 3 as well. Hooray!

The problem case is the end user who uses lib xyz, and lib xyz does not get updated, even though we've submitted pull requests and bought the author a bottle of Octomore (which is not particularly inexpensive, but oh, so delicious!). That end user can not upgrade to RSpec 3 without:

a) bagging lib xyz. Boo!
b) using RSpec.current_example to expose example so lib xyz works even though lib xyz's author didn't update it yet. Hooray!

Make sense?

That solves the Capybara issue, btw, without imposing anything on Capybara in the short run. It just makes the end user do a little extra work until the Capybara issue is properly solved within Capybara.

Myron Marston
Owner

That solves the Capybara issue, btw, without imposing anything on Capybara in the short run. It just makes the end user do a little extra work until the Capybara issue is properly solved within Capybara.

This sounds good in theory, but the part that confuses me (and may be confuses Andy) is this:

using RSpec.current_example to expose example so lib xyz works even though lib xyz's author didn't update it yet. Hooray!

I don't see how an end user using RSpec.current_example will solve the issue of lib xyz using RSpec::ExampleGroup#example, which will raise a NoMethodError in RSpec 3. Users can use RSpec.current_example all they want to get access to the current example, but in lib xyz, as long as it calls the removed example method, it will still cause a NoMethodError to get raised.

David Chelimsky
Owner

Aha! And now I see the disconnect. Buried deep within this thread I proposed something like this:

RSpec.configure do |c|
  c.expose_current_running_example_as :example
end

And I mistook RSpec.current_example for that. So, backtracking ...

  1. I think we should include the expose_current_running_example_as config option to support the end user who uses a lib that has not upgraded to.
  2. I'm OK with or without RSpec.current_example
Myron Marston
Owner

I think we should include the expose_current_running_example_as config option to support the end user who uses a lib that has not upgraded to.

:+1:

I'm OK with or without RSpec.current_example

I think RSpec.current_example is potentially useful as a means to help libraries remain compatible with RSpec 2.x and 3.0, but it's not super important.

Jon Rowe
Collaborator

I'd rather

RSpec.configure do |c|
  c.expose_current_running_example_as :example
end

than

RSpec.current_example

(just my 2¢)

Myron Marston
Owner

I'd rather

RSpec.configure do |c|
 c.expose_current_running_example_as :example
end

than

RSpec.current_example

(just my 2¢)

They have completely different purposes. It's not an either/or.

  • expose_current_running_example_as :example config option is meant to support users who are using gems that depend on the RSpec 2 API. It's not meant for library authors to use.
  • RSpec.current_example is an idea @alindeman suggested as a possible means to assist library authors in making their code work on RSpec 2 and RSpec 3. It's not meant for end users to use.
Andy Lindeman
Owner

I'm still really confused. This is not about Capybara using RSpec for its own test suite; instead, the problem is that Capybara integrates with RSpec ... adding methods and hooks to example groups for users to drive web browsers.

When you bring in Capybara to your application or library with require 'capybara/rspec', Capybara adds hooks to your own specs that use example.

The issue is how we make those hooks compatible with both RSpec <= 2.14 and 3, without creating a rift in Capybara itself (where Capybara has to release a version that's only compatible with RSpec >= 2.14, 3). If I were a maintainer of Capybara, I'd be pretty annoyed if RSpec made me do this, since my integration with RSpec is somewhat incidental to my gem's purpose.

I think RSpec.current_example is a way to do this, but I'm open to other ideas. What do you think?

David Chelimsky
Owner

The issue is how we make those hooks compatible with both RSpec <= 2.14 and 3, without creating a rift in Capybara itself

@alindeman I don't think that the addition of RSpec.current_example is going to save 3rd party libs that use example from having to make changes to function with both rspec 2 and 3. They'll still have to something like:

if RSpec.respond_to?(:current_example)
  # use RSpec.current_example
else
  # use the local example method
end

Right?

Andy Lindeman
Owner

Right?

Yep, right. But at least there's an option, even if it's a conditional.

If we keep it as it is right now, there's nothing that doesn't feel very fragile and ugly (as far as I can tell anyway). I don't like this because there are a lot of gems that integrate optionally with RSpec (so you don't get rubygems backing up the version constraint) and because many gems integrate with RSpec as a convenience for users, even when that's not a core feature. Making a breaking change like this and giving no good option to support both versions feels like we're not being a very good citizen.

David Chelimsky
Owner

Yep, right. But at least there's an option, even if it's a conditional.
If we keep it as it is right now, there's nothing that doesn't feel very fragile and ugly (as far as I can tell anyway).

I agree we don't have a better alternative at the moment. I read your concern about Capybara to mean that you thought there was a way that Capy wouldn't need any changes at all to properly support rspec-3.

I don't like this because there are a lot of gems that integrate optionally with RSpec (so you don't get rubygems backing up the version constraint) and because many gems integrate with RSpec as a convenience for users, even when that's not a core feature. Making a breaking change like this and giving no good option to support both versions feels like we're not being a very good citizen.

Agree with all of that ^^ as well, so I think we're on the same page at this point re: RSpec.current_example: we should add it unless we can come up with a better option for lib authors. That is a separate issue from end-users who use libs that haven't adapted, which could be resolved with config.expose_current_example_as :xxx, which should be documented as an interim measure. You on board w/ that @alindeman?

Andy Lindeman
Owner

Agree with all of that ^^ as well, so I think we're on the same page at this point re: RSpec.current_example: we should add it unless we can come up with a better option for lib authors. That is a separate issue from end-users who use libs that haven't adapted, which could be resolved with config.expose_current_example_as :xxx, which should be documented as an interim measure. You on board w/ that @alindeman?

Yes, these two things sound good to me :) I'm sorry for all the misunderstandings throughout this thread.

I'll make a PR for RSpec.current_example shortly. I'll open an issue for expose_current_example_as to make sure it doesn't get lost too.

Ohno Shin'ichi shin1ohno referenced this pull request from a commit in shin1ohno/autodoc December 19, 2013
Ohno Shin'ichi Make Rspec 3 compliant
As of RSpec 3, we can't call example in test.
see discussion: rspec/rspec-core#666
61dc733
Ohno Shin'ichi shin1ohno referenced this pull request in r7kamura/autodoc December 19, 2013
Open

Make Rspec 3 compliant #7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
11  lib/rspec/core/example.rb
@@ -111,7 +111,7 @@ def run(example_group_instance, reporter)
111 111
             with_around_each_hooks do
112 112
               begin
113 113
                 run_before_each
114  
-                @example_group_instance.instance_eval(&@example_block)
  114
+                @example_group_instance.instance_exec(self, &@example_block)
115 115
               rescue Pending::PendingDeclaredInExample => e
116 116
                 @pending_declared_in_example = e.message
117 117
               rescue Exception => e
@@ -233,13 +233,8 @@ def fail_with_exception(reporter, exception)
233 233
       end
234 234
 
235 235
       # @private
236  
-      def instance_eval(*args, &block)
237  
-        @example_group_instance.instance_eval(*args, &block)
238  
-      end
239  
-
240  
-      # @private
241  
-      def instance_eval_with_rescue(context = nil, &block)
242  
-        @example_group_instance.instance_eval_with_rescue(context, &block)
  236
+      def instance_exec_with_rescue(context = nil, &block)
  237
+        @example_group_instance.instance_exec_with_rescue(self, context, &block)
243 238
       end
244 239
 
245 240
       # @private
55  lib/rspec/core/example_group.rb
@@ -55,6 +55,7 @@ def description
55 55
         #   @param [String] name
56 56
         #   @param [Hash] extra_options
57 57
         #   @param [Block] implementation
  58
+        #   @yield [Example] the example object
58 59
         def self.define_example_method(name, extra_options={})
59 60
           define_method(name) do |*all_args, &block|
60 61
             desc, *args = *all_args
@@ -67,10 +68,22 @@ def self.define_example_method(name, extra_options={})
67 68
         end
68 69
 
69 70
         # Defines an example within a group.
  71
+        # @example
  72
+        #   example do
  73
+        #   end
  74
+        #
  75
+        #   example "does something" do
  76
+        #   end
  77
+        #
  78
+        #   example "does something", :with => 'additional metadata' do
  79
+        #   end
  80
+        #
  81
+        #   example "does something" do |ex|
  82
+        #     # ex is the Example object that evals this block
  83
+        #   end
70 84
         define_example_method :example
71 85
         # Defines an example within a group.
72  
-        #
73  
-        # @see example
  86
+        # @example
74 87
         define_example_method :it
75 88
         # Defines an example within a group.
76 89
         # This is here primarily for backward compatibility with early versions
@@ -79,17 +92,23 @@ def self.define_example_method(name, extra_options={})
79 92
         define_example_method :specify
80 93
 
81 94
         # Shortcut to define an example with `:focus` => true
  95
+        # @see example
82 96
         define_example_method :focus,   :focused => true, :focus => true
83 97
         # Shortcut to define an example with `:focus` => true
  98
+        # @see example
84 99
         define_example_method :focused, :focused => true, :focus => true
85 100
 
86 101
         # Shortcut to define an example with :pending => true
  102
+        # @see example
87 103
         define_example_method :pending,  :pending => true
88 104
         # Shortcut to define an example with :pending => 'Temporarily disabled with xexample'
  105
+        # @see example
89 106
         define_example_method :xexample, :pending => 'Temporarily disabled with xexample'
90 107
         # Shortcut to define an example with :pending => 'Temporarily disabled with xit'
  108
+        # @see example
91 109
         define_example_method :xit,      :pending => 'Temporarily disabled with xit'
92 110
         # Shortcut to define an example with :pending => 'Temporarily disabled with xspecify'
  111
+        # @see example
93 112
         define_example_method :xspecify, :pending => 'Temporarily disabled with xspecify'
94 113
 
95 114
         # Works like `alias_method :name, :example` with the added benefit of
@@ -439,16 +458,24 @@ def self.set_ivars(instance, ivars)
439 458
         ivars.each {|name, value| instance.instance_variable_set(name, value)}
440 459
       end
441 460
 
442  
-      # @attr_reader
443  
-      # Returns the {Example} object that wraps this instance of
444  
-      # `ExampleGroup`
445  
-      attr_accessor :example
  461
+      def initialize
  462
+        @_current_rspec_example = nil
  463
+      end
  464
+
  465
+      def example=(current_example)
  466
+        @_current_rspec_example = current_example
  467
+      end
  468
+
  469
+      # @deprecated use a block argument
  470
+      def example
  471
+        RSpec.deprecate("example", :replacement => "a block argument")
  472
+        @_current_rspec_example
  473
+      end
446 474
 
447  
-      # @deprecated use {ExampleGroup#example}
  475
+      # @deprecated use a block argument
448 476
       def running_example
449  
-        RSpec.deprecate("running_example",
450  
-                        :replacement => "example")
451  
-        example
  477
+        RSpec.deprecate("running_example", :replacement => "a block argument")
  478
+        @_current_rspec_example
452 479
       end
453 480
 
454 481
       # Returns the class or module passed to the `describe` method (or alias).
@@ -468,12 +495,12 @@ def described_class
468 495
       # @private
469 496
       # instance_evals the block, capturing and reporting an exception if
470 497
       # raised
471  
-      def instance_eval_with_rescue(context = nil, &hook)
  498
+      def instance_exec_with_rescue(example, context = nil, &hook)
472 499
         begin
473  
-          instance_eval(&hook)
  500
+          instance_exec(example, &hook)
474 501
         rescue Exception => e
475  
-          raise unless example
476  
-          example.set_exception(e, context)
  502
+          raise unless @_current_rspec_example
  503
+          @_current_rspec_example.set_exception(e, context)
477 504
         end
478 505
       end
479 506
     end
4  lib/rspec/core/hooks.rb
@@ -18,7 +18,7 @@ def options_apply?(example_or_group)
18 18
 
19 19
       class BeforeHook < Hook
20 20
         def run(example)
21  
-          example.instance_eval(&block)
  21
+          example.instance_exec(example, &block)
22 22
         end
23 23
 
24 24
         def display_name
@@ -28,7 +28,7 @@ def display_name
28 28
 
29 29
       class AfterHook < Hook
30 30
         def run(example)
31  
-          example.instance_eval_with_rescue("in an after hook", &block)
  31
+          example.instance_exec_with_rescue("in an after hook", &block)
32 32
         end
33 33
 
34 34
         def display_name
6  lib/rspec/core/memoized_helpers.rb
@@ -195,8 +195,10 @@ def let(name, &block)
195 195
 
196 196
           # Apply the memoization. The method has been defined in an ancestor
197 197
           # module so we can use `super` here to get the value.
198  
-          define_method(name) do
199  
-            __memoized.fetch(name) { |k| __memoized[k] = super(&nil) }
  198
+          if block.arity == 1
  199
+            define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(@_current_rspec_example, &nil) } }
  200
+          else
  201
+            define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(&nil) } }
200 202
           end
201 203
         end
202 204
 
13  lib/rspec/core/pending.rb
@@ -76,7 +76,7 @@ def pending_fixed?; true; end
76 76
       #         # ...
77 77
       #       end
78 78
       def pending(*args)
79  
-        return self.class.before(:each) { pending(*args) } unless example
  79
+        return self.class.before(:each) { pending(*args) } unless @_current_rspec_example
80 80
 
81 81
         options = args.last.is_a?(Hash) ? args.pop : {}
82 82
         message = args.first || NO_REASON_GIVEN
@@ -85,18 +85,17 @@ def pending(*args)
85 85
           return block_given? ? yield : nil
86 86
         end
87 87
 
88  
-        example.metadata[:pending] = true
89  
-        example.metadata[:execution_result][:pending_message] = message
  88
+        @_current_rspec_example.metadata[:pending] = true
  89
+        @_current_rspec_example.metadata[:execution_result][:pending_message] = message
90 90
         if block_given?
91 91
           begin
92 92
             result = begin
93 93
                        yield
94  
-                       example.example_group_instance.instance_eval { verify_mocks_for_rspec }
95  
-                       true
  94
+                       @_current_rspec_example.example_group_instance.instance_eval { verify_mocks_for_rspec }
96 95
                      end
97  
-            example.metadata[:pending] = false
  96
+            @_current_rspec_example.metadata[:pending] = false
98 97
           rescue Exception => e
99  
-            example.execution_result[:exception] = e
  98
+            @_current_rspec_example.execution_result[:exception] = e
100 99
           ensure
101 100
             teardown_mocks_for_rspec
102 101
           end
58  spec/rspec/core/example_group_spec.rb
@@ -65,8 +65,8 @@ def metadata_hash(*args)
65 65
         examples_run = []
66 66
         group = ExampleGroup.describe("parent") do
67 67
           describe("child") do
68  
-            it "does something" do
69  
-              examples_run << example
  68
+            it "does something" do |ex|
  69
+              examples_run << ex
70 70
             end
71 71
           end
72 72
         end
@@ -79,13 +79,13 @@ def metadata_hash(*args)
79 79
         it "runs its children " do
80 80
           examples_run = []
81 81
           group = ExampleGroup.describe("parent") do
82  
-            it "fails" do
83  
-              examples_run << example
  82
+            it "fails" do |ex|
  83
+              examples_run << ex
84 84
               raise "fail"
85 85
             end
86 86
             describe("child") do
87  
-              it "does something" do
88  
-                examples_run << example
  87
+              it "does something" do |ex|
  88
+                examples_run << ex
89 89
               end
90 90
             end
91 91
           end
@@ -663,19 +663,10 @@ def define_and_run_group(define_outer_example = false)
663 663
         end
664 664
       end
665 665
 
666  
-      it "has no 'running example' within before(:all)" do
667  
-        group = ExampleGroup.describe
668  
-        running_example = :none
669  
-        group.before(:all) { running_example = example }
670  
-        group.example("no-op") { }
671  
-        group.run
672  
-        expect(running_example).to be(nil)
673  
-      end
674  
-
675 666
       it "has access to example options within before(:each)" do
676 667
         group = ExampleGroup.describe
677 668
         option = nil
678  
-        group.before(:each) { option = example.options[:data] }
  669
+        group.before(:each) {|ex| option = ex.options[:data] }
679 670
         group.example("no-op", :data => :sample) { }
680 671
         group.run
681 672
         expect(option).to eq(:sample)
@@ -684,20 +675,11 @@ def define_and_run_group(define_outer_example = false)
684 675
       it "has access to example options within after(:each)" do
685 676
         group = ExampleGroup.describe
686 677
         option = nil
687  
-        group.after(:each) { option = example.options[:data] }
  678
+        group.after(:each) {|ex| option = ex.options[:data] }
688 679
         group.example("no-op", :data => :sample) { }
689 680
         group.run
690 681
         expect(option).to eq(:sample)
691 682
       end
692  
-
693  
-      it "has no 'running example' within after(:all)" do
694  
-        group = ExampleGroup.describe
695  
-        running_example = :none
696  
-        group.after(:all) { running_example = example }
697  
-        group.example("no-op") { }
698  
-        group.run
699  
-        expect(running_example).to be(nil)
700  
-      end
701 683
     end
702 684
 
703 685
     %w[pending xit xspecify xexample].each do |method_name|
@@ -755,20 +737,20 @@ def define_and_run_group(define_outer_example = false)
755 737
     describe Object, "describing nested example_groups", :little_less_nested => 'yep' do
756 738
 
757 739
       describe "A sample nested group", :nested_describe => "yep" do
758  
-        it "sets the described class to the described class of the outer most group" do
759  
-          expect(example.example_group.described_class).to eq(ExampleGroup)
  740
+        it "sets the described class to the described class of the outer most group" do |ex|
  741
+          expect(ex.example_group.described_class).to eq(ExampleGroup)
760 742
         end
761 743
 
762  
-        it "sets the description to 'A sample nested describe'" do
763  
-          expect(example.example_group.description).to eq('A sample nested group')
  744
+        it "sets the description to 'A sample nested describe'" do |ex|
  745
+          expect(ex.example_group.description).to eq('A sample nested group')
764 746
         end
765 747
 
766  
-        it "has top level metadata from the example_group and its parent groups" do
767  
-          expect(example.example_group.metadata).to include(:little_less_nested => 'yep', :nested_describe => 'yep')
  748
+        it "has top level metadata from the example_group and its parent groups" do |ex|
  749
+          expect(ex.example_group.metadata).to include(:little_less_nested => 'yep', :nested_describe => 'yep')
768 750
         end
769 751
 
770  
-        it "exposes the parent metadata to the contained examples" do
771  
-          expect(example.metadata).to include(:little_less_nested => 'yep', :nested_describe => 'yep')
  752
+        it "exposes the parent metadata to the contained examples" do |ex|
  753
+          expect(ex.metadata).to include(:little_less_nested => 'yep', :nested_describe => 'yep')
772 754
         end
773 755
       end
774 756
 
@@ -826,12 +808,12 @@ def define_and_run_group(define_outer_example = false)
826 808
         expect(@before_all_top_level).to eq('before_all_top_level')
827 809
       end
828 810
 
829  
-      it "can access the before all ivars in the before_all_ivars hash", :ruby => 1.8 do
830  
-        expect(example.example_group.before_all_ivars).to include('@before_all_top_level' => 'before_all_top_level')
  811
+      it "can access the before all ivars in the before_all_ivars hash", :ruby => 1.8 do |ex|
  812
+        expect(ex.example_group.before_all_ivars).to include('@before_all_top_level' => 'before_all_top_level')
831 813
       end
832 814
 
833  
-      it "can access the before all ivars in the before_all_ivars hash", :ruby => 1.9 do
834  
-        expect(example.example_group.before_all_ivars).to include(:@before_all_top_level => 'before_all_top_level')
  815
+      it "can access the before all ivars in the before_all_ivars hash", :ruby => 1.9 do |ex|
  816
+        expect(ex.example_group.before_all_ivars).to include(:@before_all_top_level => 'before_all_top_level')
835 817
       end
836 818
 
837 819
       describe "but now I am nested" do
43  spec/rspec/core/example_spec.rb
@@ -27,7 +27,7 @@ def capture_stdout
27 27
     $stdout = orig_stdout
28 28
   end
29 29
 
30  
-  it 'can be pretty printed' do
  30
+  it "can be pretty printed" do
31 31
     output = ignoring_warnings { capture_stdout { pp example_instance } }
32 32
     expect(output).to include("RSpec::Core::Example")
33 33
   end
@@ -164,26 +164,26 @@ def assert(val)
164 164
     end
165 165
   end
166 166
 
167  
-  describe '#described_class' do
  167
+  describe "#described_class" do
168 168
     it "returns the class (if any) of the outermost example group" do
169 169
       expect(described_class).to eq(RSpec::Core::Example)
170 170
     end
171 171
   end
172 172
 
173 173
   describe "accessing metadata within a running example" do
174  
-    it "has a reference to itself when running" do
175  
-      expect(example.description).to eq("has a reference to itself when running")
  174
+    it "has a reference to itself when running" do |ex|
  175
+      expect(ex.description).to eq("has a reference to itself when running")
176 176
     end
177 177
 
178  
-    it "can access the example group's top level metadata as if it were its own" do
179  
-      expect(example.example_group.metadata).to include(:parent_metadata => 'sample')
180  
-      expect(example.metadata).to include(:parent_metadata => 'sample')
  178
+    it "can access the example group's top level metadata as if it were its own" do |ex|
  179
+      expect(ex.example_group.metadata).to include(:parent_metadata => 'sample')
  180
+      expect(ex.metadata).to include(:parent_metadata => 'sample')
181 181
     end
182 182
   end
183 183
 
184 184
   describe "accessing options within a running example" do
185  
-    it "can look up option values by key", :demo => :data do
186  
-      expect(example.metadata[:demo]).to eq(:data)
  185
+    it "can look up option values by key", :demo => :data do |ex|
  186
+      expect(ex.metadata[:demo]).to eq(:data)
187 187
     end
188 188
   end
189 189
 
@@ -333,7 +333,7 @@ def run_and_capture_reported_message(group)
333 333
         expect(message).to match(/An error occurred in an after.* hook/i)
334 334
       end
335 335
 
336  
-      it 'does not print mock expectation errors' do
  336
+      it "does not print mock expectation errors" do
337 337
         group = RSpec::Core::ExampleGroup.describe do
338 338
           example do
339 339
             foo = double
@@ -422,7 +422,7 @@ def run_and_capture_reported_message(group)
422 422
     end
423 423
   end
424 424
 
425  
-  it 'does not interfere with per-example randomness when running examples in a random order' do
  425
+  it "does not interfere with per-example randomness when running examples in a random order" do
426 426
     values = []
427 427
 
428 428
     RSpec.configuration.order = :random
@@ -436,4 +436,25 @@ def run_and_capture_reported_message(group)
436 436
 
437 437
     expect(values.uniq).to have(2).values
438 438
   end
  439
+
  440
+  describe "optional block argument" do
  441
+    it "contains the example" do |ex|
  442
+      expect(ex).to be_an(RSpec::Core::Example)
  443
+      expect(ex.description).to match(/contains the example/)
  444
+    end
  445
+  end
  446
+
  447
+  %w[example running_example].each do |accessor|
  448
+    describe accessor do
  449
+      it "is deprecated" do
  450
+        expect(RSpec).to receive(:deprecate)
  451
+        send(accessor)
  452
+      end
  453
+
  454
+      it "returns the current running example" do |ex|
  455
+        allow(RSpec).to receive(:deprecate)
  456
+        expect(send(accessor)).to eq ex
  457
+      end
  458
+    end
  459
+  end
439 460
 end
33  spec/rspec/core/memoized_helpers_spec.rb
@@ -56,6 +56,18 @@ def subject_value_for(describe_arg, &block)
56 56
     end
57 57
 
58 58
     describe "explicit subject" do
  59
+      it "yields the example in which it is eval'd" do
  60
+        example_yielded_to_subject = nil
  61
+        example_yielded_to_example = nil
  62
+
  63
+        group = ExampleGroup.describe
  64
+        group.subject { |e| example_yielded_to_subject = e }
  65
+        group.example { |e| subject; example_yielded_to_example = e }
  66
+        group.run
  67
+
  68
+        expect(example_yielded_to_subject).to eq example_yielded_to_example
  69
+      end
  70
+
59 71
       [false, nil].each do |falsy_value|
60 72
         context "with a value of #{falsy_value.inspect}" do
61 73
           it "is evaluated once per example" do
@@ -184,6 +196,18 @@ def define_and_run_group
184 196
       end
185 197
 
186 198
       describe "with a name" do
  199
+        it "yields the example in which it is eval'd" do
  200
+          example_yielded_to_subject = nil
  201
+          example_yielded_to_example = nil
  202
+
  203
+          group = ExampleGroup.describe
  204
+          group.subject(:foo) { |e| example_yielded_to_subject = e }
  205
+          group.example       { |e| foo; example_yielded_to_example = e }
  206
+          group.run
  207
+
  208
+          expect(example_yielded_to_subject).to eq example_yielded_to_example
  209
+        end
  210
+
187 211
         it "defines a method that returns the memoized subject" do
188 212
           list_value_1 = list_value_2 = subject_value_1 = subject_value_2 = nil
189 213
 
@@ -516,6 +540,15 @@ def count
516 540
       expect(@nil_value_count).to eq(1)
517 541
     end
518 542
 
  543
+    let(:yield_the_example) do |example_yielded_to_let|
  544
+      @example_yielded_to_let = example_yielded_to_let
  545
+    end
  546
+
  547
+    it "yields the example" do |example_yielded_to_example|
  548
+      yield_the_example
  549
+      expect(@example_yielded_to_let).to equal example_yielded_to_example
  550
+    end
  551
+
519 552
     let(:regex_with_capture) { %r[RegexWithCapture(\d)] }
520 553
 
521 554
     it 'does not pass the block up the ancestor chain' do
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.