4

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?

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
dev
  • 61
  • 1
  • 5
  • Why do you need to `patch`? Can't you just pass an instance of the mock to the function/class that needs it, instead of the real implementation? – ndc85430 Jun 03 '22 at 14:15
  • The function I am testing does not take an instance of the real database object. In the file that the function is used, there is an import for the real DataBase client, which is instantiated by the function. – dev Jun 03 '22 at 14:41

2 Answers2

2

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.

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
dev
  • 61
  • 1
  • 5
0

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.

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • Hi, I appreciate your answer. I had forgotten to specify that this attribute is defined in __init__(). Also, print(mock) shows , which tells me mock is not an instance of the class but the class itself – dev Jun 03 '22 at 14:39