doubles icon indicating copy to clipboard operation
doubles copied to clipboard

Doubles and contextlib

Open markuswustenberg opened this issue 9 years ago • 4 comments

I'm trying to use doubles with a class that uses the @contextmanager decorator on a method: https://docs.python.org/2/library/contextlib.html

However, this fails because __enter__ and __exit__ are not properly handled on the double. Any way to add this functionality to doubles? Or am I holding it wrong?

I would expect something like this to work:

from contextlib import contextmanager
from doubles import expect, ObjectDouble

class Stats(object):
    @contextmanager
    def timed(self, key):
        pass

def test__timed_does_stuff():
    d = ObjectDouble(Stats())
    expect(d).timed.once()
    with d.timed('mykey'):
        pass

markuswustenberg avatar Oct 11 '16 09:10 markuswustenberg

Currently the only way to do this would be to mock enter/exit manually on your object double.

e.g.

expect(d).__enter__ 
expect(d).__exit__

I need to think a little bit if doing this automagically is a feature we want to add.

toddsifleet avatar Oct 17 '16 00:10 toddsifleet

@toddsifleet Thanks for your answer. My problem is complicated by the fact that there are multiple methods on the Stats class I want to test/mock and only one that has the @contextmanager decorator:

class Stats(object):
    @contextmanager
    def timed(self, key):
        pass

    def count(self, key):
        pass

So if I mock the whole Stats class using e.g. ObjectDouble (so stats_mock = ObjectDouble(Stats())), I'm not actually sure how I mock the __enter__ and __exit__ separately on just one method?

markuswustenberg avatar Oct 17 '16 09:10 markuswustenberg

@markuswustenberg I couldn't think of a good way to do this...but I something like this would work:

from contextlib import contextmanager

from doubles import expect, ObjectDouble

class Stats(object):
    @contextmanager
    def timed(self, key):
        pass

    def count(self, key):
        print 'count called'


class MyTimed(object):
    def __init__(self, key):
        self.key = key

    def __enter__(self, *args, **kwargs):
        print self.key
        return self

    def __exit__(self, *args, **kwargs):
        print self.key

my_double = ObjectDouble(Stats)
expect(my_double).timed.and_return_result_of(lambda x: MyTimed(x))


with my_double.timed('foobar') as foo:
    print foo

Output:

$ python test.py
foobar
<__main__.MyTimed object at 0x109d0cbd0>
foobar

I will try to think how we can generalize this and add it to doubles.

toddsifleet avatar Oct 17 '16 20:10 toddsifleet

Cool, thanks a lot!

markuswustenberg avatar Oct 18 '16 05:10 markuswustenberg