Bryan Cheng
Bryan Cheng

Reputation: 433

Proper way to return mocked object using pytest.fixture

I'm trying to setup the target under test in @pytest.fixture and use it in all my tests in the module. I'm able to patch the test correctly, but after I add the @pytest.fixture to return the mock object and invoke the mocked object in other unit tests the object starting to refer back to the original function.

Following is the code I have. I was expecting the mocked_worker in the unit test to refer to the return value, but it is invoking the actual os.getcwd method instead.

Please help me correct the code:

import os
import pytest
from unittest.mock import patch

class Worker:
    def work_on(self):
        path = os.getcwd()
        print(f'Working on {path}')
        return path

@pytest.fixture()
def mocked_worker():
    with patch('test.test_module.os.getcwd', return_value="Testing"):
        result = Worker()
    return result

def test_work_on(mocked_worker):
    ans = mocked_worker.work_on()
    assert ans == "Testing"

Upvotes: 37

Views: 62523

Answers (3)

Romano
Romano

Reputation: 1

Another way to create a mock fixture without any additional package like pytest-mock is to use the following code convention:

import pytest
from unittest.mock import patch
from imagine import Worker

# in this example I want my fixture to be module scope since I want the 
# fixture will be called once during all module tests execution.

@pytest.fixture(scope="module")
@patch("test.test_module.os.getcwd")
def model_checker(mock_instance):
    mock_monitor.return_value = "Testing"
    worker_instance = Worker()
    return worker_instance

Please notice that the order which I put the decorators (@) is very important. You must first call the @patch and only after it you can put the rest of the decorators (in our case it's a pytest fixture). To understand more about the decorators order logic, I advise you to read more about Decorators & MagicMock in the python documentation.

Upvotes: -1

The Hog
The Hog

Reputation: 969

I would recommend to use pytest-mock. So full example of one file (test_file.py) solution using this library would be:

import os
import pytest
from unittest.mock import patch

class Worker:
    def work_on(self):
        path = os.getcwd()
        print(f'Working on {path}')
        return path

@pytest.fixture()
def mocked_worker(mocker):  # mocker is pytest-mock fixture
    mocker.patch('test_file.os.getcwd', return_value="Testing")

def test_work_on(mocked_worker):
    worker = Worker()  # here we create instance of Worker, not mock itself!!
    ans = worker.work_on()
    assert ans == "Testing"

used libraries for reference:

pytest==5.3.0
pytest-mock==1.12.1

Upvotes: 32

eyllanesc
eyllanesc

Reputation: 243945

The problem is that when the worker returns the scope of "with" statement ends making the object take its real value, the solution is to use "yield".

@pytest.fixture()
def mocked_worker():
    with patch('test.test_module.os.getcwd', return_value="Testing"):
        result = Worker()
        yield result

Upvotes: 66

Related Questions