I'm trying to use Pytest to test Python code. The code I'm testing isn't a class but is a bunch of functions in the same file that call other functions in the same file. For example advanced_function1 calls basic_function, and advanced_function2 can also call basic_function.
When I file.basic_function = Mock()
in a test it isn't scoped to just that test.
I can verify this because when I run the test (test_advanced_function1) that mocks basic_function then test_advanced_function2 does not pass. However if I only run test_advanced_function2 (meaning basic_function never gets mocked in another test) then it works.
To my knowledge the way to correct this is to use a context manager in test_advanced_function1 and mock basic_function as that context manager. I don't know how to do this.
To simplify things and more directly declare intent I've got a test that checks if the function is a Mock object or not.
myfile.py
def basic_function():
return True
def advanced_function1():
return basic_function()
def advanced_function2():
return not basic_function()
test_myfile.py
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
myfile.basic_function = Mock(return_value='foo')
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
So, how do I use a context manager in test_advanced_function1 to mock basic_function?
EDIT: To clarify, I can't assign the mock as a fixture because there are multiple test cases that run in the real code I'm working on and I can't mock basic_function for all the asserts in advanced_function1. Yes I know I should break this massive test apart into smaller tests but I'm just learning the codebase and don't want to change too much before getting all their tests working again.
CodePudding user response:
The mock.patch
context manager is seldom used directly in pytest. Instead we use fixtures. The plugin pytest-mock
provides a fixture for using the mock.patch
API in a more "pytest-thonic" way, like this:
import pytest
import myfile
def test_basic_function():
assert myfile.basic_function() == True
def test_advanced_function1(mocker):
mocker.patch("myfile.basic_function", return_value="foo")
assert myfile.advanced_function1() == 'foo'
def test_advanced_function2():
assert myfile.advanced_function2() == False
CodePudding user response:
One possible solution is to use patch in a context manager. test_myfile.py
from unittest.mock import patch
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
with patch('myfile.basic_function') as basic_function:
basic_function.return_value = 'foo'
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
But please let me know if there's a more elegant way to do this!