A more specific any_instance...

已查看 453 次
跳至第一个未读帖子

Lawrence Forooghian

未读,
2011年7月25日 08:47:592011/7/25
收件人 mocha-developer
Hi,

I've been wondering if it's possible to restrict any_instance to only
match instances meeting some criteria. I was thinking of a syntax
similar to the way in which you can give 'with' a matching_block -
that is, something like this:

(Customer.any_instance { |inst| inst.name ==
'Dave' }).expects(:foo).with(:bar),

where the expectation is only met if we call x.foo(:bar) where x is
some instance of Customer with name Dave.

(Sorry if I'm not using the correct Mocha terminology by the way - I
haven't really had much of a look at what objects are used by Mocha
behind the scenes to make everything work. It may also be that what I
am asking about doesn't really gel with the way Mocha works.)

It doesn't look like this exists - is this because you don't think
there's a sensible use case for it, or just because it hasn't been
implemented? Or is there some other way to get the same functionality?
If it doesn't exist already, I'd be happy to have a go at implementing
it.

Thanks,
Lawrence

James Mead

未读,
2011年7月25日 10:57:332011/7/25
收件人 mocha-d...@googlegroups.com
Hi Lawrence,

Thanks for your email and your interest in Mocha.

I think it would be possible to add what you are describing, but it would probably be a little awkward. At the moment when you stub a method using any_instance, you are actually changing the definition of the method on the relevant Ruby class. Naturally this affects *all* classes.

If you wanted to make this selective, I think you'd need to change the method declaration in Mocha::AnyInstanceMethod#define_new_method [1] to include an if/else statement. The condition would provide the selectivity and if it didn't match you'd need to call the original (now hidden) method. If such functionality was added to Mocha, I think I'd want it to use something similar to the Mocha::ParameterMatchers [2] used in the Mocha::Expectation#with rather than the more generic block functionality you've described.

Personally I don't tend to use the any_instance functionality very much. If you feel the need to use it, I think it tends to mean either (a) you are testing the internals of a library which you can't easily change; or (b) the design of your code could be improved to make it more test-able. In scenario (a), I would tend to try to avoid such testing, perhaps by introducing a thin adaptor for the 3rd party library as described in the "Don't mock third-party libraries" section of [2]. In scenario (b), I would prefer to change the design of my code to make it more test-able and avoid the need for any_instance in my tests.

One alternative you may not have considered is to stub the new (or any other class builder) method on the relevant class and then use the existing "with" selectivity to return different instances. These different instances can either be real instances (with relevant methods stubbed) or mock instances. Here's an example [4] which should demonstrate what I mean.

In summary, although I think it would be possible, I'm not totally convinced that such functionality is desirable in Mocha. However, I'm willing to be persuaded. The best way to persuade me is to provide a concrete example where the alternatives are even less desirable.

I hope that helps.

Regards, James.
----
http://jamesmead.org/

[1] https://github.com/floehopper/mocha/blob/master/lib/mocha/any_instance_method.rb#L27
[2] http://mocha.rubyforge.org/classes/Mocha/ParameterMatchers.html
[3] http://www.mockobjects.com/2007/04/test-smell-everything-is-mocked.html
[4] https://gist.github.com/1104229

> --
> You received this message because you are subscribed to the Google Groups "mocha-developer" group.
> To post to this group, send email to mocha-d...@googlegroups.com.
> To unsubscribe from this group, send email to mocha-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/mocha-developer?hl=en.
>

Lawrence Forooghian

未读,
2011年7月25日 12:23:102011/7/25
收件人 mocha-developer
Hi James,

Thanks very much for getting back to me so quickly and for your
detailed response.

I've put together a quick example of where I might use this type of
functionality [1]. I fear that the example might be too simplistic to
demonstrate what I'm trying to get at - if this is the case, then
please let me know and I'll try to come up with a more realistic
example. But I think that the general situation in which I would find
this useful would be 'an entity is represented by a different in-
memory object in the test than it is in the code that is being
tested'.

I think that in this particular case, the most pertinent of your
suggestions would be the one of stubbing out the factory method; in
this instance I think that would be Customer.find_by_email_address.
I'm certainly more than happy to be told that I'm wrong in my testing
approach here, but personally I'd feel a bit uneasy stubbing out
ActiveRecord finders.

I like the suggestion of using some sort of analogue of the
ParameterMatchers if this feature were to be implemented. Would you
want those to be in addition to the generic block syntax, or would you
just want the matchers? I think it would be useful to have the option
of either.

It'd be great to hear your thoughts.

Thanks,
Lawrence

[1] https://gist.github.com/991f8aaeec2300c16b4c

On Jul 25, 3:57 pm, James Mead <floehop...@gmail.com> wrote:
> Hi Lawrence,
>
> Thanks for your email and your interest in Mocha.
>
> I think it would be possible to add what you are describing, but it would probably be a little awkward. At the moment when you stub a method using any_instance, you are actually changing the definition of the method on the relevant Ruby class. Naturally this affects *all* classes.
>
> If you wanted to make this selective, I think you'd need to change the method declaration in Mocha::AnyInstanceMethod#define_new_method [1] to include an if/else statement. The condition would provide the selectivity and if it didn't match you'd need to call the original (now hidden) method. If such functionality was added to Mocha, I think I'd want it to use something similar to the Mocha::ParameterMatchers [2] used in the Mocha::Expectation#with rather than the more generic block functionality you've described.
>
> Personally I don't tend to use the any_instance functionality very much. If you feel the need to use it, I think it tends to mean either (a) you are testing the internals of a library which you can't easily change; or (b) the design of your code could be improved to make it more test-able. In scenario (a), I would tend to try to avoid such testing, perhaps by introducing a thin adaptor for the 3rd party library as described in the "Don't mock third-party libraries" section of [2]. In scenario (b), I would prefer to change the design of my code to make it more test-able and avoid the need for any_instance in my tests.
>
> One alternative you may not have considered is to stub the new (or any other class builder) method on the relevant class and then use the existing "with" selectivity to return different instances. These different instances can either be real instances (with relevant methods stubbed) or mock instances. Here's an example [4] which should demonstrate what I mean.
>
> In summary, although I think it would be possible, I'm not totally convinced that such functionality is desirable in Mocha. However, I'm willing to be persuaded. The best way to persuade me is to provide a concrete example where the alternatives are even less desirable.
>
> I hope that helps.
>
> Regards, James.
> ----http://jamesmead.org/
>
> [1]https://github.com/floehopper/mocha/blob/master/lib/mocha/any_instanc...

David Chelimsky

未读,
2011年8月6日 09:22:542011/8/6
收件人 mocha-d...@googlegroups.com
Hi Lawrence (and James),

The problem you've detailed in your gist stems from the fact that ActiveRecord < 3.1 doesn't offer an in memory identity map. This particular problem will go away with 3.1, in which the following will pass:

dave = Customer.create! :email_address => 'da...@foo.bar'
assert_same dave, Customer.find_by_email_address 'da...@foo.bar'

That said, how about intercepting the call rather than constraining it?

Customer.any_instance.expects(:deactivate) do
assert_equals 'da...@foo.bar', email_address
end

That would work if the block is executed in the context of the customer object, and would require extending 'expects', but no change to 'any_instance'.

Alternatively:

Customer.any_instance.expects(:deactivate) do |customer|
assert_equals 'da...@foo.bar', customer.email_address
end

Here mocha passes the customer receiving the message to the block. The first one aligns better w/ rspec-mocks, so people who use both libs would have an easier time going back and forth, but the latter feels more intention revealing.

Cheers,
David

Cheers,
David

回复全部
回复作者
转发
0 个新帖子