Python:模拟上下文管理器

Wil*_*lem 50 python mocking

我不明白为什么我不能在这个例子中模拟NamedTemporaryFile.name:

from mock import Mock, patch
import unittest
import tempfile

def myfunc():
    with tempfile.NamedTemporaryFile() as mytmp:
        return mytmp.name

class TestMock(unittest.TestCase):
    @patch('tempfile.NamedTemporaryFile')
    def test_cm(self, mock_tmp):
        mytmpname = 'abcde'
        mock_tmp.__enter__.return_value.name = mytmpname
        self.assertEqual(myfunc(), mytmpname)
Run Code Online (Sandbox Code Playgroud)

测试结果如下:

AssertionError: <MagicMock name='NamedTemporaryFile().__enter__().name' id='140275675011280'> != 'abcde'
Run Code Online (Sandbox Code Playgroud)

Mic*_*ico 91

您正在设置错误的模拟:mock_tmp不是上下文管理器,而是返回上下文管理器.将设置行替换为:

mock_tmp.return_value.__enter__.return_value.name = mytmpname
Run Code Online (Sandbox Code Playgroud)

你的测试会奏效.


Fid*_*tix 39

为了扩展纳撒尼尔的答案,这个代码块

with tempfile.NamedTemporaryFile() as mytmp:
    return mytmp.name
Run Code Online (Sandbox Code Playgroud)

有效地做三件事

# Firstly, it calls NamedTemporaryFile, to create a new instance of the class.
context_manager = tempfile.NamedTemporaryFile()  

# Secondly, it calls __enter__ on the context manager instance.
mytmp = context_manager.__enter__()  

# Thirdly, we are now "inside" the context and can do some work. 
return mytmp.name
Run Code Online (Sandbox Code Playgroud)

当您替换tempfile.NamedTemporaryFile为或的实例时MockMagicMock

context_manager = mock_tmp()
# This first line, above, will call mock_tmp().
# Therefore we need to set the return_value with
# mock_tmp.return_value

mytmp = context_manager.__enter__()
# This will call mock_tmp.return_value.__enter__() so we need to set 
# mock_tmp.return_value.__enter__.return_value

return mytmp.name
# This will access mock_tmp.return_value.__enter__.return_value.name
Run Code Online (Sandbox Code Playgroud)

  • 这个答案非常简洁,对我很有帮助。谢谢。我正在使用 contextlib.asynccontextmanager ,为了使其正常工作,我唯一需要更改的就是将 `__enter__` 更改为 `__aenter__` (2认同)

Pet*_*r K 6

下面是pytestmocker Fixture的替代方案,这也是常见的做法:

def test_myfunc(mocker):
    mock_tempfile = mocker.MagicMock(name='tempfile')
    mocker.patch(__name__ + '.tempfile', new=mock_tempfile)
    mytmpname = 'abcde'
    mock_tempfile.NamedTemporaryFile.return_value.__enter__.return_value.name = mytmpname
    assert myfunc() == mytmpname
Run Code Online (Sandbox Code Playgroud)


小智 5

使用 pytest 和模拟装置扩展 Peter K 的答案。

def myfunc():
    with tempfile.NamedTemporaryFile(prefix='fileprefix') as fh:
        return fh.name


def test_myfunc(mocker):
    mocker.patch('tempfile.NamedTemporaryFile').return_value.__enter__.return_value.name = 'tempfilename'
    assert myfunc() == 'tempfilename'
Run Code Online (Sandbox Code Playgroud)