Closed iain closed 11 years ago
This would help solve #116, as it's essentially the same delegate issue we've now dealt with in rspec-expectations.
This would not be usable without rspec-expectations. I don't know how problematic this is, but it is a feature of rspec-mocks.
I can think of some ways to make this work w/o depending on rspec-expectations using feature detection.
Basically, RR[1] provides the cleanest solution to this issue. I would like to see something like this in rspec-mocks:
stub(User).all { the_users } # uses `method_missing`
stub(User, :all) { the_users } # does not use `method_missing`
mock(User).all # an "expectation" that `all` will be called
double('the user') # same as the current `double`
To utilize stub
and mock
as in the example above, it would cause a massive breaking change. Totally worth it IMO.
/cc @dchelimsky
I thought about this a bit more, and I came up an idea that's similar, but avoids the massive breakage:
stubbing(User, :all) do
the_users
end
mocking(User, :all)
The "-ing" forms aren't taken up by anything yet, and read nicely when used with the block form. It looks a little weird when you're just mocking it w/o an implementation, though.
The feedback on the recent syntax changes in rspec-expectations have been overwhelmingly positive, and I think the fact that we didn't break anything is a big part of that. I don't think massive breaking change is acceptable at this point, unless we decide to do it in a separate gem (e.g. rspec-better-mocks
or whatever).
On a side note, if we can come up with a good solution here, we have the potential to get rspec in a place where the monkey patching it does is very, very minimal. Up to now, RSpec has added the following methods to every object in the system:
should
and should_not
have been dealt with w/ the expect
syntax, and I just fixed rspec core in rspec/rspec-core#638 to no longer add describe
to every object. The methods added by rspec-mocks are the last major source of monkey patching ever object, so if we can find a good solution here it would be great :).
The "ing" idiom suggests to me "scoped within this block" e.g.
stubbing(User, :all => [User.new]) do
# stub is scoped to this block
end
# that stub is no longer available
I actually have a never-used-lib that uses this syntax for that purpose.
Also, I'd prefer to have a syntax that aligns with the changes to rspec-expectations if we're going to do this. I'd be OK with:
stub(object, :method => value)
stub(object, :method) { lazy_value }
expect(object).to receive(:message)
I would not be OK with:
stub(object).method { lazy_value }
mock(object).method { lazy_value }
This syntax is no more terse than the others, but it is not intention revealing IMO.
@dchelimsky -- I like your thinking here, but won't stub(object, :method => value)
be a breaking change? Currently the stub
method within an example creates a new test double; here it stubs a method on the given object.
This wouldn't just be a breaking change; this would be an incredibly confusing breaking change. Removing a method, so that users start getting NoMethodErrors
is one thing; taking an existing method, and completely changing it's semantics so that it does something entirely different will be far more difficult for users to deal with. They'll get confusing failures and won't have an easy way to scan their code to find the old uses of stub
.
@myronmarston STOP MAKING SENSE! :)
Yes, we'll need a new name, but that's the form I want to use: some_new_name(object, :method => value)
.
One option would be allow
(comes from one of the java mock frameworks - can't recall which and google is not my friend today).
Maybe stub_method(object, :method => value)
?
Is it necessary to change stub
at the same time? I know we want to fix the bug for stubbing, but should mocks wait for this?
@myronmarston For some reason I dislike the word method
in there. I like the fact that mocks use the vocabulary of sending and receiving messages. Not sure on an alternative though.
Any objections to allow
? Then we'd have:
allow(object, :method => value)
allow(object, :method) { lazy_value }
expect(object).to receive(:message)
Looks great!
Any objections to allow?
A couple concerns I have:
allow
makes sense on a pure mock object, because you'll get errors unless you mock or allow every method called on the object. But it makes very little sense on real objects. You're always allowed to call every method on a real object. When you're stubbing a method on a real object in order to force it's return value, allow
doesn't really fit.Here's another idea: respond_to
:
respond_to(object, :method => value)
respond_to(object, :method) { lazy_value }
respond_to(object, :method).with(some_argument) { lazy_value }
When you stub a method, you're setting how an object responds to a particular message.
Unfortunately, respond_to(object, :method).with(:foo)
sounds like "respond to object.method by returning foo"
Yeah, I just realized that. API design is hard :(.
More challenging when you have legacy to support :)
Guys, how about this small idea...
Currently I write a lot of things like:
it "calculates thing weekly" do
Calculator.should_receive(:annual_revenue).with(year: 5).and_return 520
report.weekly_revenue.should == 10 # 520/52
end
The should_receive
syntax is just a bit harder to read and type than what my eye & fingers want to:
it "calculates thing weekly" do
Calculator.should_receive.annual_revenue(year: 5) { 520 }
report.weekly_revenue.should == 10 # 520/52
end
Please consider this syntax or similar if it is something you think aligns with RSpec philosophy.
@dnagir that's an interesting idea, but chaining the target method onto should_receive
is too different from everything else we do in RSpec IMO. My guess is that what you're looking for is something like RR's syntax, which lets you use the same syntax for the method once you wrap the object: mock(Calculator).annual_revenue(year: 5) { 520 }
. That's very nice in terms of mirroring part of the actual use, but I think it's also a bit disconnected due to the mix of wrapping the object. I don't have a better idea at this point, but I'm not in favor of should_receive.annual_revenue
(though I am personally in favor of receiving annual revenue).
@dchelimsky how about fake
instead of stub
?
Or pretend
?
fake
already has special meaning: http://xunitpatterns.com/Fake%20Object.html
pretend
is interesting, but I don't like it initially - gut feeling. Need to think about it some more.
Another possibility: stub_message
--it's a bit like my earlier suggestion (stub_method
) but retains the vocabulary of sending and receiving messages.
@myronmarston the message is what comes in, the method is how it responds.
on_receipt_of(object, message).with(arguments)
?
Actually, I was just thinking about the original allow
idea some more. allow
would allow us (pardon the pun) to do:
expect(object).to receive(:message)
allow(object).to receive(:message)
I'm still concerned about the potential confusion of the language of "allowing" a real object to receive a message it can already receive anyway, but the symmetry here is really, really nice and I might actually like this one best after all since none of the other ideas have that symmetry and we haven't yet come up with one we're happy with.
@myronmarston agreed so far, but open to other suggestions that maintain the same symmetry without the confusion about what allow
means in the context of a real object.
I love the idea to use a consistent syntax for the mocks. And giving the stubs a different name, once the syntax changes, makes sense too. But allow
does not sound that natural to me.
Maybe something like adjust
or augment
could work.
expect(object).to receive(:message)
augment(object).to receive(:message)
When staying close to the should_receive
syntax, I would prefer something like 'simulate' or 'implements':
Object.simulate(:method).with(param).and_return xyz
Object.implements(:method).with(param).and_return xyz
What about implement
rather than allow
? It makes sense for real objects.
implement(object, :method) { :bar }
I like the sense given by implement
, but it doesn't read well as implement(object).to receive(:message)
, and I'd really like to have the symmetry of the same basic syntax.
augment
(suggested by @mediafinger above) is pretty good, I think. It's certainly better for the case of a real object than allow
, although I think for a pure mock object, allow
still reads better.
So here's an idea: we can provide all 3 of these:
# sets a mock expectation
expect(object).to receive(:message)
# stubs the method
allow(object).to receive(:message)
augment(object).to receive(:message)
Basically, one of allow
and augment
would be an alias for the other, allowing the user to use whichever makes the most sense in their context (typically, augment
for real objects and allow
for pure mock objects).
We're not augmenting it to receive a message, but to allow it to receive the message. The more I think of this I like allow
, and I definitely don't want to add two names for a new feature. FWIW, I thought of "allow" because JMock uses "allowing", so it comes from the mock-library space (just not Ruby).
We're not augmenting it to receive a message, but to allow it to receive the message. The more I think of this I like allow, and I definitely don't want to add two names for a new feature. FWIW, I thought of "allow" because JMock uses "allowing", so it comes from the mock-library space (just not Ruby).
That's certainly true for a pure mock object (which is why I like allow
a lot for that case), but IMO allow
only makes sense if the object cannot already respond to the message--and with a real object that may not be the case. augment
seems like a better word for real objects to me, but even it doesn't really have the right connotation.
I see what you're saying about real objects, but "augment this object to receive message x" doesn't make any sense based on similar arguments: it can already receive x. I don't have a good answer, but here are some more bad answers to see if any spark better ideas:
tell(object).to respond_to(:message).with(value)
redef(object, :message) { value }
replace(object, :message) { value }
I hate all three :)
I've been thinking recently about the design benefits of pure mock objects. Partial mocks/stubs (i.e. on real objects) don't provide the same design and isolation benefits.
So...I think it makes sense to optimize rspec-mocks for the pure mock case while allowing its use on real objects. allow
does this nicely: it reads really, really well for pure mock objects, and a bit funny for real objects. It's a subtle nudge that it's generally better to design your system and tests to work with pure mock objects.
All that is to say: I dislike your 3 suggestions as well, and I'm getting on board with adding expect
/allow
for mocks/stubs in spite of the funny wording for real objects.
+1 for allow
. I like that it's borrowed from jMock, so it isn't Ruby-specific vocabulary. I agree with @myronmarston that making partial mocking ugly isn't necessarily bad. It should be discouraged.
the explanation given by @myronmarston makes sense to me! +1
I'm also not a big fan of #allow
, especially for stubbing methods on real objects (as opposed to pure stub/mock objects). How about #stub_on
. I like the way that would read: stub_on( my_object, some_method: 'blah' )
.
I think I could live with stub_on
as long as we also use expect_on
so the methods align. WDYT?
stub_on
and expect_on
align nicely. But does it work with the rest of the fluent interface (e.g. with(some, args)
, exactly(3).times
, etc? I'd like to preserve as much of that fluent interface as possible.
Again w/ the sense. That won't work at all, and I'm at a bit of a loss for new ideas. So far, I think expect/allow is the best pairing. More ideas?
Why does that not work at all?
stub_on( my_object, foo: 'bar' )
expect_on( my_object, :foo ).with( 'some', 'args' ).exactly( 3 ).times
# or
expect_on( my_object ).message( :foo ).with( 'some', 'args' ).exactly( 3 ).times
(Not sure #message
in the last line would be the best method name, but it illustrates the point.)
Another possibility: separate the injection of the methods from the definition of the stubs.
allow_stubbing_on( my_object )
my_object.stubs( foo: 'bar' )
Or, what about #override
instead of (or in addition to) #allow
.
(Just brainstorming here.)
How about something like this:
on(obj).expect(message).with(args).and_return(val)
on(obj).stub(message).and_return(val)
The new syntax is only on
and expect
. Everything else stays the same. Is on
too likely to general (i.e. likely to create conflicts)?
@jwilger re my_object.stubs( foo: 'bar' )
, we've already got three ways to define return values, so I don't want to add another as part of this.
obj.stub(m => v)
obj.stub(m) { v }
obj.stub(m).and_return(v)
Nice thing about on
is all that just changes to:
on(obj).stub(m => v)
on(obj).stub(m) { v }
on(obj).stub(m).and_return(v)
I'm open to a different word, but I'm quickly growing attached to the idea of adding a single wrapper function to allow us not to have to add methods to all objects. This is what flexmock and RR both do (though their syntax is a bit different) and I'm a big fan.
I agree that the idea of having a single wrapper method is the way to go. I'm not a fan of #on
, as it doesn't reveal much about what it actually does.
re: my_object.stub(foo: 'bar')
; Unless it's about the 's' that I accidentally added to the end of the #stub
(I've been working a lot on a project that uses Mocha), I'm not sure what you mean, since that's equivalent to obj.stub(m => v)
.
I missed the trailing colon. I thought you were saying obj.stub(m, v)
. Never mind :)
Some other possibilities:
mask(obj).stub(m => v)
mask(obj).expect(m).and_return(v)
screen(obj).stub(m => v)
screen(obj).expect(m).and_return(v)
disguise(obj).stub(m => v)
disguise(obj).expect(m).and_return(v)
relieve(obj).stub(m => v)
relieve(obj).expect(m).and_return(v)
take_over(obj).stub(m => v)
take_over(obj).expect(m).and_return(v)
simulate(obj).stub(m => v)
simulate(obj).expect(m).and_return(v)
mimic(obj).stub(m => v)
mimic(obj).expect(m).and_return(v)
affect(obj).stub(m => v)
affect(obj).expect(m).and_return(v)
Out of those, I think I like #mask
the best. When adding a mock or stub method to a real object, you are, in effect, applying a mask. The method name is short, easy to spell, and reveals the intention pretty well.
A huge +1 to @dchelimsky's idea of on
. I like how it reads a lot. I like that it allows us to preserve the rest of the current fluent interface. I like that it's a very short word (so it doesn't make the code much longer--in fact, on(foo).expect
is shorter than foo.should_receive
. I like that it works for mocks and stubs.
One side comment: this isn't really fulfilling @iain's original request of using the new rspec-expectations syntax for mocks, but we've seen how difficult it is to come up with a way of using that syntax for mocks and stubs, and I'm more interested in solving the issue of adding stub
and should_receive
to every object (with the proxy/delegate issues that creates) than in mirroring the syntax of rspec-expectations.
Is on too likely to general (i.e. likely to create conflicts)?
I think it's actually so general that it's unlikely to create conflicts :). That sounds like an oxymoron, but on
is a word on the level of the
or an
or with
--it's a grammar word (a preposition in this case) that on its own doesn't really have any meaning, without the surrounding context of other words. I think it only makes sense to be used as part of a fluent interface. I can't imagine anyone having a stand-alone spec helper method called on
--it reveals no intention whatsoever. Given that it makes little sense outside of a fluent interface, I find it unlikely that end users will have on
defined in their example groups.
That said, if we want to be conservative, we could play it safe and wait to introduce this until 3.0, or introduce it in a future 2.x release, but with the syntax options (similar to rspec-expectations') defaulted to only the current syntax. However, if we do this, I'd like to get it out in a 2.x release with the options defaulting to both syntaxes being available, so the rest of the 2.x can be a transitionary period to the time when we default the old syntax off (potentially 3.0, but that's obviously very, very open to discussion and change).
I like on
too, but it moves away from a more unified syntax. Now this has never really been the case: it's should_receive
and not should receive
.
For the same reason, I don't like that the new expect
syntax doesn't use blocks, as in: it would be neat if expect { obj }.to eq 5
was also supported. But that is besides the point right now.
We've been coming up with alternative syntaxes for quite a while now, but I fear mocking and stubbing is drifting too far apart from the rest of the RSpec syntax.
We could just deprecate stub
in RSpec 2, and change the meaning in RSpec 3. That way we can use expect(object).to receive(method)
for mocks and stub(object, :method => value)
. We got 2 other aliases for stub anyway. Too radical?
I like on too, but it moves away from a more unified syntax.
FWIW, I've been thinking about the unified syntax possibilites of expect(obj).to receive
a bit today as this discussion goes on, and while I like that it is unified with rspec-expectations, I've been thinking it's probably a good thing to not re-use the same syntax for something entirely different. expect(obj).to matcher
fails the example (or not) based on the current state of obj
. expect(obj).to receive
looks the same but would do something entirely different. It's not confusing for those of us who have been participating in this conversation, but in general, similar-looking constructs should behave similarly and different-looking constructs should behave differently. It could lead to a great deal of confusion on the part of new RSpec users.
For the same reason, I don't like that the new expect syntax doesn't use blocks, as in: it would be neat if
expect { obj }.to eq 5
was also supported. But that is besides the point right now.
It could probably theoretically be supported, but I'm not sure what the advantage is. Plus, the new expect syntax does support blocks, for when you are expecting something will happen as the result of running a bit of code (e.g. change
, raise_error
, throw_symbol
, yield_control
, etc). I think it would be confusing to support a block for value expectations when a block is already used for block expectations. That's a different discussion, though :).
We could just deprecate stub in RSpec 2, and change the meaning in RSpec 3. That way we can use expect(object).to receive(method) for mocks and stub(object, :method => value). We got 2 other aliases for stub anyway. Too radical?
Even though I'm thinking that unity of syntax w/ rspec-expectations isn't desired here, I do think unity of syntax of mocks and stubs are, particularly when the fluent interface comes into play. It's not clear to me how the stub(object, :method => value)
syntax would support that. And changing the meaning of stub
would lead to greater pain for those upgrading than introducing a new method (like on
) will.
I'm definitely +1 for David's on
suggestion
Some further thinking of mine regarding on
:
on(obj).expect(:message)
, we could do on(obj).mock(:message)
. That has some nice symmetry with on(obj).stub(:message)
(since we call them "mocks" vs "stubs", not "expects" vs "stubs"). The fact that both are 4 letters means they would line up visually nicely as well. I think it's also closer the vocabulary I use in conversation when discussing tests: in conversations with coworkers, I use mock
as a verb all the time (e.g. "I mocked the foo method"). Lastly, given the important of expect
for rspec-expectations now, not using it here could be beneficial in terms of using expect
for just one thing in rspec (rather than for 2 distinct things).unstub
available the same way: on(obj).unstub(:previously_stubbed_message)
.should_not_receive
. I don't have any ideas at the moment, though. Neither on(obj).dont_mock
nor on(obj).dont_expect
read right. Anyone have an idea here?on
. on
could take a block for cases like these:on(obj) do |o|
o.mock(:foo) { "foo value" }
o.stub(:bar) { "bar value" }
end
mock
/expect
and stub
available directly on the object w/o the need to wrap them with on
, since they're "owned by RSpec", so to speak, and don't suffer from the delegate/proxy issues. However, it might be good to keep things consistent and, if the on
syntax is used, force it to be used on pure mock objects as well. I'm not sure where I fall on this one yet. Regardless, I think we'd still want to support declaring stubs as a message/return-val hash when creating the mock (e.g. double("my double", :foo => 7)
, and even if we allowed setting mocks or stubs directly on the mock object, it should still also work with on
to allow for helper methods that can operate on either a real object or a mock object.These are all just current thoughts of mine...I'm not wedded to any of this.
On the fence re: "mock". Agree it is concise, and most people will know what it means. The problem is that 10 people will answer "what does mock mean" differently.
Regardless of the word we end up w/, I like on(obj) {|o| ...}
and think it should apply to real objects and doubles (what you're referring to as "pure mocks") alike.
re: unstub
, what do you think about changing that to restore
in the context of on
(aliased to unstub
for compatibilty): on(obj).restore(:previously_stubbed_method)
re: negative expectations, expect
actually works quite nicely: on(obj).expect(:message).never
More thoughts?
If you want to use the new expectation syntax, then the current mock syntax will look out of place.
That's why I propose we introduce the following matcher:
Some considerations:
stub
. It's not an expectation/assertion, so I don't think it needs to change.