0

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.

Jason
  • 15,915
  • 3
  • 48
  • 72
  • Actually, only one class is under test, - `HelperClass` never gets created or called as it's being mocked! – Jason Dec 09 '22 at 18:34

1 Answers1

0

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.

Jason
  • 15,915
  • 3
  • 48
  • 72