psyho / bogus

Fake library for Ruby
Other
359 stars 14 forks source link

Entity return values and contracts #64

Open tpendragon opened 9 years ago

tpendragon commented 9 years ago

So I have service objects which have methods that return new entities - builders are a good example. However, if I stub out that service object to return a fake of the class it's supposed to return (or even a blank instance of the same class!) when I verify the contract it says it failed.

I can sort of understand why - but I'm concerned that forcing me to return nicely equivalent entities will result in complicated collaboration testing.

psyho commented 9 years ago

This is a problem with contract tests that I'm aware of. Returning "real looking" data is sometimes problematic, because it leads to "noisy" tests (a lot of test body is responsible for creating that data). This problem is relatively easy to work around if you use "fixtures" (named helpers for returning built objects).

Sometimes, like you mentioned, the equality rules for the real looking data mean that two objects built in the exact same way are not equal (like User.new != User.new if User is an ActiveRecord model), which complicates things even further.

A solution that might work (but I haven't implemented in Bogus yet), is having some sort of placeholder.

I'd imagine the API looking like this:

stub(FooBuilder).build(any_args) { instance_of(:foo) }

This would return the same thing as fake(:foo), but with overwritten equality rules, so that it would be the same as any instance of Foo (or whatever class is configured to be copied by fake(:foo)).

What do you think, would that fix the problem you're having? Do you have any other ideas how this might be solved?

tpendragon commented 9 years ago

Thanks for looking into this. I think the case where I return an object is moderately rare (except maybe in the case of class calls, and there's no support for simultaneous class/instance contract verification) and your API would probably work for that case.

I ran into something similar yesterday though. If I put an expectation on a dependency (service1) to be called with a second dependency (user) as an argument, the contract for service1 can't ever be fulfilled. I write a test for service1 such that if given a mock user (which is a double of the same class, with the same stubs) it returns an appropriate value - however, two fakes are never equivalent unless they have the same object ID (as far as I can tell), and so the argument signatures don't match. It would probably be a good idea for fake1 to equal fake2 if all their stubbed interactions and class matches.