I have a class which works approximately like this:
class Foo:
def __init__(self, iterable: List[...]):
self.iterable = iterable
def __getitem__(self, i: int):
return self.iterable[i]
def __iter__(self):
return iter(self.iterable)
So the indexing and iteration behavior operates on an attribute iterable
(which is a list) without needing to explicitly reference that attribute. For example:
>>> foo = Foo(iterable=['a', 'b', 'c'])
>>> foo[-1]
'c'
>>> for x in foo:
... print(x)
...
a
b
c
My current approach to testing this class uses unittest.mock.patch
like this:
def test_foo():
mock_foo = patch('my_module.Foo', autospec=True)
mock_foo.iterable = ['a', 'b', 'c']
mock_foo.__getitem__ = lambda self, i: self.iterable[i]
mock_foo.__iter__ = lambda self: iter(self.iterable)
So I simply create the patch, then define the attributes and dunders necessary to replicate the behavior of the target class.
However, when I attempt to index into or iterate over the patch, it doesn't work:
>>> mock_foo[-1]
TypeError: '_patch' object is not subscriptable
>>> list(mock_project_export_set)
TypeError: '_patch' object is not iterable
This is unexpected and confusing, because when I invoke the dunders directly, everything works as desired:
>>> mock_foo.__getitem__(mock_foo, -1)
'c'
>>> list(mock_foo.__iter__(mock_foo))
['a', 'b', 'c']
Why the inconsistency? How can I replicate the behavior of my class in the test?
I'm not super experienced with patching/mocking, so if there's an altogether superior and simpler way, I'd love to know.
Python 3.11.3
My testing framework is pytest
.
It looks like, you testing the patch
instead of the MagicMock
from the documentation of unittest.mock
from unittest.mock import patch
def test_foo():
patcher = patch('my_module.foo.Foo', autospec=True) # depending on your structure
mock_foo_class = patcher.start()
mock_foo_class.iterable = ['a', 'b', 'c']
mock_foo_class.__getitem__ = lambda self, i: self.iterable[i]
mock_foo_class.__iter__ = lambda self: iter(self.iterable)
An alternative approach would be using the patch as a decorator, depending on what u trying to accomplish:
from unittest.mock import patch
@patch('my_module.foo.Foo') # depending on your structure
def test_foo(mock_foo):
mock_foo.iterable = ['a', 'b', 'c']
mock_foo.__getitem__ = lambda self, i: self.iterable[i]
mock_foo.__iter__ = lambda self: iter(self.iterable)
assert mock_foo[-1] == 'c'
assert list(mock_foo) == mock_foo.iterable