Reputation: 929
For testing purposes I would like to mock shutil.which (Python 3.5.1), which is called inside a simplified method find_foo()
def _find_foo(self) -> Path:
foo_exe = which('foo', path=None)
if foo_exe:
return Path(foo_exe)
else:
return None
I'm using pytest for implementing my test cases. Because of that I also would like to use the pytest extension pytest-mock. In the following I pasted an example testcase using pytest + pytest-mock:
def test_find_foo(mocker):
mocker.patch('shutil.which', return_value = '/path/foo.exe')
foo_path = find_foo()
assert foo_path is '/path/foo.exe'
This way of mocking with pytest-mock doesn't work. shutil.which is still called instead of the mock.
I tried to directly use the mock package which is now part of Python3:
def test_find_foo():
with unittest.mock.patch('shutil.which') as patched_which:
patched_which.return_value = '/path/foo.exe'
foo_path = find_foo()
assert foo_path is '/path/foo.exe'
Sadly the result is the same. Also shutil.which()
is called instead of specified mock.
Which steps of successfully implementing a mock are wrong or missed in my test cases?
Upvotes: 4
Views: 5181
Reputation: 929
I investigated more time studying unittest.mock and pytest-mock. I found a simple solution without modifying the production code using the patch decorator. In the following I pasted a code snippet demonstrating a third approach with pytest-mock:
def test_find_foo(mocker):
mocker.patch('__main__.which', return_value='/path/foo.exe')
foo_path = find_foo()
assert foo_path == Path('/path/foo.exe')
Without pytest-mock (plain unittest-mock and a @patch decorator) this solution is also working. The important line in the code snippet above is
mocker.patch('__main__.which', return_value='/path/foo.exe')
The patch decorator expects the name (full path) of the function which will be called from the system under test. This is clearly explained in the mock documentation. The following paragraph summarizes this principle of the patch decorator:
patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.
Upvotes: 4
Reputation: 55972
Injecting the which
method into your method or object would allow you to mock the dependency without pytest-mock
.
def _find_foo(self, which_fn=shutil.which) -> Path:
foo_exe = which_fn('foo', path=None)
if foo_exe:
return Path(foo_exe)
else:
return None
def test_find_foo():
mock_which = Mock(return_value = '/path/foo.exe')
foo_path = obj._find_foo(which_fn=mock_which)
assert foo_path is '/path/foo.exe'
Upvotes: 1
Reputation: 490
Try using monkeypatch. You can see in the examples how they "monkeypatch" os.getcwd
to return the wanted path. In your case I think that this should work:
monkeypatch.setattr("shutil.which", lambda: "/path/foo.exe")
Upvotes: 1