dev
dev

Reputation: 61

How do you use patch() as a context manager?

I have a class that mocks database functionality which does not subclass Mock or MagicMock because it defines its own __init__() method:

class DatabaseMock():
    def __init__(self, host=None):
        self.host = host
        self.x = {}
   
    # other methods that mutate x

There is a function I want to test that makes an API call to the real database, so I patched it out:

from unittest.mock import patch
class TestFunctions():
    def test_function(self):
        with patch("path.to.database.call", DatabaseMock) as mock:
            result = function_i_am_testing()
            assert mock.x == result

There is a field of the DatabaseMock called x, but in the patch context, mock.x returns an AttributeError. This leads to me believe mock is not really an instance of DatabaseMock(). Also, I had tried making x a class level object which makes x visible, but its state would persist through separate test calls which I do not want.

What is mock and how can I reference the mocked object instance in the context?

Upvotes: 4

Views: 7848

Answers (2)

dev
dev

Reputation: 61

I have figured out the issue. When patch is given a class, it will return a class, not an object instance of that class.

So in my example, mock is not a DataBaseMock object instance, but a reference to the class. This is why class level variables are visible, but not object instance fields.

In order to get my desired functionality, I did this:

from unittest.mock import patch
class TestFunctions():
    def test_function(self):
        with patch("path.to.database.call") as mock:
            mock.return_value = DataBaseMock()
            result = function_i_am_testing()
            assert mock.return_value.x == result    

Now, mock is a MagicMock object, whose return value is the object I need.

Upvotes: 2

Samwise
Samwise

Reputation: 71517

You are indeed calling patch correctly, so the problem may be with your DatabaseMock (which does not have an x attribute in the code you've provided), or perhaps with your actual test function.

Here's a simple example demonstrating that mock (the object returned by the context manager) is created by calling the new argument, and takes the place of the patch target within the context:

>>> class Foo:
...     x = 0
...
>>> class FooMock:
...     x = 42
...
>>> from unittest.mock import patch
>>> with patch("__main__.Foo", FooMock) as mock:
...     print(mock.x)
...     print(Foo.x)
...
42
42
>>> print(Foo.x)
0

If you still have doubts about what mock is, try adding a print(mock) to your test function.

Upvotes: 1

Related Questions