Mocking a Standard Library function with and without pytest-mock

試著忘記壹切 提交于 2019-12-24 07:41:14

问题


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?


回答1:


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.




回答2:


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")



回答3:


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'


来源:https://stackoverflow.com/questions/38159765/mocking-a-standard-library-function-with-and-without-pytest-mock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!