Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Provide `self` (the instance) to #any_instance blocks #175

Closed
zettabyte opened this Issue · 18 comments
@zettabyte

Feature Request

For both stubs and method expectations on #any_instance, where the user provides a block implementation, it would be great if there were a way to get at the actual instance on which the method actually gets called.

One possible way would be to include the instance as an extra parameter to the block, at the end of the list, if the arity of the block indicates the user's block expects to receive this extra instance parameter (and leave it off otherwise).

@zettabyte zettabyte referenced this issue from a commit
@zettabyte zettabyte Added scenarios for part of feature request #175
Specifically specify that stubs on #any_instance that provide a block
implementation a) work as it and b) can optionally receive as a
parameter to the block the instance on which the stubbed method was
called.
4f67b47
@zettabyte zettabyte referenced this issue from a commit
@zettabyte zettabyte Added more scenarios for feature request #175
Specifically specify that message expectations on #any_instance that
provide a block implementation a) work as they do now and b) support
providing the instance on which the method gets called to the block if
it is written to expect it.
fd52251
@patmaddox

hrm...I can see how this is useful. I wonder if the instance should go first so you can use *args as the second block param.

Although this brings to mind the discussion in #23 - wonder if some info about the stubbed out object & method should be exposed inside the stub block somehow.

@benfyvie

+1 I really need this functionality and can see how it would be beneficial to others as well

@coderanger

Obligatory :+1: on this, just spent hours trying to figure out a good way around not having this because Rails filters are evil.

@Exoth

+1
This functionality would be very useful to me.

@JonRowe
Owner

Hey @zettabyte how are you getting along with this?

@zettabyte

@JonRowe: I'm still all for this feature. Early on after I first reported it I attempted an implementation so I could submit a pull request for consideration. However, I'd need to spend more time (than I've had available) to get it to actually work; I'm completely unfamiliar with rspec's code.

An aside, every time I see the issue title with the word this I'm reminded that I was busy doing a lot of javascript at the time this came up (should be self). :-)

@myronmarston
Owner

I'd still like to get this in; the hardest part is that if we just change to this it'll be a backwards incompatibility, because the args yielded to the block implementation will change. I think that to make this change we'll have to provide a config option that defaults to off, so it reminds 100% backwards compatible.

@dblock

+1

@JonRowe
Owner

@myronmarston what about if we evaluated the blocks in the context of the instance, thus making self the reference to the instance?

@myronmarston

@JonRowe -- there's a long discussion about that possibility in #236; please see that for the existing discussion, and add your thoughts there.

@JonRowe
Owner

Ah ok it was considered before ;)

@kdank

+1

@myronmarston

We're still planning on this for RSpec 3. There's a comment from #236 (which we decided not to merge) that I thought would be useful to quote here so folks know the direction we'll be going with this and the reasons:

Anyhow, you've taken a quite different (but interesting) approach here to what I had in mind. Originally I was thinking it would work like this:

# normally, this is how it works...
MyClass.any_instance.stub(:foo) do |arg_1, arg_2, &block|
end

# but if you change the config...
RSpec::Mocks.configure do |c|
  c.yield_instance_to_any_instance_implementation_blocks = true
end

# ...then the yielded args change to:
MyClass.any_instance.stub(:foo) do |my_class_instance, arg_1, arg_2, &block|
end

I think the implementation of this approach would be much, much simpler: no need to mess with defining a method dynamically, or eval'ing the block in a different context or anything...we just add a simple conditional that checks the value of the config option, and, if it is true, prepends the yield args list with the object instance before calling the implementation block.

On top of that, I think it could be potentially troublesome to change the eval context in the implementation block like that. It's a much bigger change (as you suddenly can't call helper methods defined in the example group like you could before) compared to just yielding an extra arg; plus it's more inline with how block implementations work elsewhere in rspec-mocks.

In my opinion, we should have included the object instance in the yielded args from the very start. We didn't think things through to realize that without this, users have no way to get a reference to the stubbed object. Given that, I'd like to move in the direction of making the object instance always be yielded from the any_instance block implementations. We can change the spec_helper.rb generate by rspec --init so that it turns this on by default for new projects, and we can consider making it default to on in some future major release (e.g. maybe RSpec 3 or 4), while still allowing folks to disable it as needed. Making it a global config is far simpler than adding to the fluent interface, and essentially "corrects" the oversite that caused us to not include the object instance in the first place.

It's true that folks who want to use this feature would need to turn on the config option, and go through and fix all their existing any_instance block implementations, but that doesn't bother me too much...we're providing a new feature that users have to opt-in to use on an existing project, and I would expect it to be pretty easy to use an editor macro to go through a fix all uses of any_instance block implementations in a short time period.

BTW, we briefly discussed a similar addition to the fluent interface to configure what args a particular block implementation is yielded, but decided against it for various reasons. The discussion is here:

#23 (comment)

@JonRowe
Owner

Yeah for the record, recent experience has taught me that there's no way we should evaluate the blocks so that self is the instance, providing it to the block as an argument is a far better solution both technically and for developer sanity :)

@myronmarston

This has been fixed by @samphippen and will be the default behavior in RSpec 3.

@yujinakayama yujinakayama referenced this issue in yujinakayama/transpec
Closed

Support conversion of any_instance block #31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.