beanbaginc / kgb

Python function spy support for unit tests
MIT License
48 stars 5 forks source link

using call_fake to return different responses #3

Closed guyisra closed 5 years ago

guyisra commented 5 years ago

I'd like my spy to be a double agent, and return different responses each time being called

self.spy_on(obj.func, call_fake=fake_multi)

where

def fake_multi(*args, **kwargs):
    return [True, False]

such that

obj.func.calls[0].return_value == True
obj.func.calls[1].return_value == False

Does kgb deploy double (and more) agents?

chipx86 commented 5 years ago

There are two ways to do what you want.

  1. If you're controlling the call order of the function being spied on, then you can call .unspy() on your function and then re-spy with a new fake function returning the next result.
  2. You can track the call order within the fake function and return the result based on the current number of calls, possibly asserting while you're at it that the arguments being provided are what you expect for that iteration of the call.
guyisra commented 5 years ago

Thanks for the quick reply I'm currently doing #2, but it feels funky. Will a PR for adding a multiple response flag to the spy_on be accepted?

chipx86 commented 5 years ago

I'd have to give that further thought, but right now I'm not sure it's something I'd really like to include. The reason is that this leads to a slippery slope of specialized spy behavior.

Adding support for specifying multiple callbacks to spy_on seems reasonable at first glance, but then you hit edge cases off of that, like possible needs to loop through those callbacks, or specifying how many times each should run before advancing to the next, or conditionally calling one of them based on some other criteria (argument contents instead of call order).

On the other hand, if this is a pattern needed a lot in your testing, it's fairly easy to write your own wrapper. We do this for some argument-based conditional spys in Review Board. For example:

class MyBaseTestCase(SpyAgency, TestCase):
    def spy_on_multi(self, orig_func, call_fake_funcs):
        def _my_spy(*args, **kwargs):
            i = len(orig_func.calls)
            self.assertLess(i, len(call_fake_funcs))

            return call_fake_funcs[i](*args, **kwargs)

        self.spy_on(orig_func, call_fake=_my_spy)

Something like that. Completely untested.

That way, you can adjust it as your requirements change, without needing additional changes put into kgb itself.

Regarding contributions, we review all code for kgb over on reviews.reviewboard.org, rather than using GitHub Pull Requests.

guyisra commented 5 years ago

👍 thanks