I'm attempting to write a unit test using MagicMock.
I have 2 classes that are being tested:
class HelperClass:
@property
def output(self) -> dict[str, Any]:
return self._output
def __enter__(self):
print("__enter__")
self._output: dict[str, Any] = {}
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
print("__exit__")
class ImportantClass:
def fn(self, key: str):
with self._fac() as hc:
if key not in hc.output:
raise RuntimeError("bad bad not good")
return hc.output[key]
def __init__(self, fac: Callable[[], HelperClass]):
self._fac = fac
ImportantClass
is initialized with a factory method that can be used to create instances of HelperClass
. Here's the test code
def test_important_class():
hc = MagicMock(spec=HelperClass)
omock = PropertyMock(return_value={"foo": "bar", "baz": "quux"})
type(hc).output = omock
assert hc.output["foo"] == "bar"
assert hc.output["baz"] == "quux" # these assertions succeed
assert OuterClass(fac=lambda: hc).fn("foo") == "bar"
assert OuterClass(fac=lambda: hc).fn("baz") == "quux" # these don't
When I run through the code in the debugger the type of hc.output
while in the test is dict[str: str]
, but when I step into the fn
method, the type of hc.output
is MagicMock
.
Update
I found this question which shows that the issue I'm facing is related to the implicit ContextManager
that is being created when calling with self._fac() as hc
.
I'm still having a tough time updating the test, so that the ContextManager
gets mocked appropriately.
Finally got it, I had to create a method that could be patched in the unit test
def get_mtc(self) -> MyTestClass: # this is a dummy method that will get patched
return MyTestClass()
def test_test_class():
patcher = patch("my_unit_tests.get_mtc")
fty = patcher.start()
fty.return_value.__enter__.return_value.output = {"foo": "bar", "baz": "quux"} # mocks the implicit ContextManager
assert OuterClass(fty).fn("foo") == "bar"
assert OuterClass(fty).fn("baz") == "quux"
patcher.stop()
I'd be very interested to learn a more pythonic way of doing all of this.