erwere32
erwere32

Reputation: 13

Pytest - mocking class instance passed as an argument

Let's say I have a simplified class Object:

class Object:
    def __init__(self, data):
        self.data = data

    def get_data():
        return data.copy()

And freestanding function foo:

def foo(obj: Object):
    copied_data = obj.get_data()
    ...

I want to test foo and use a fixture with mocked Object instance to pass as the argument to foo. I want the mocked object to return some predefined data so I need to mock its method as well.

How should I do this in a "proper" way with pytest? I'm not sure how to combine mocks and fixtures.

Upvotes: 1

Views: 7059

Answers (1)

Niel Godfrey P. Ponciano
Niel Godfrey P. Ponciano

Reputation: 10699

Using the with statement and the sample patching documentations:

As well as a decorator patch() can be used as a context manager in a with statement:

...

>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

We can use patch() inside a fixture and then yield the mocked instance.

src.py

class Object:
    def __init__(self, data):
        print("Real object initialized")
        self.data = data

    def get_data(self):
        print("Real object get_data")
        return self.data.copy()


def foo(obj: Object):
    print("Object instance:", obj)
    copied_data = obj.get_data()
    return copied_data

test_src.py

from unittest.mock import patch
import pytest

from src import foo, Object


@pytest.fixture
def object_instance():
    with patch('src.Object') as MockClass:
        instance = MockClass.return_value
        instance.get_data.return_value = 'bar'
        yield instance


def test_real_impl():
    object_instance = Object([1, 2])
    assert foo(object_instance) == [1, 2]


def test_mock_impl(object_instance):
    assert foo(object_instance) == 'bar'

Output:

$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
_____________________________________________________________________________________________ test_real_impl ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Real object initialized
Object instance: <src.Object object at 0x7fb59ef83820>
Real object get_data
_____________________________________________________________________________________________ test_mock_impl ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Object instance: <MagicMock name='Object()' id='140418032072736'>
2 passed in 0.06s
  • As you can see, we are able to create a mocked Object and define the return value of its methods.

Upvotes: 3

Related Questions