Python: Verspotten eines Kontextmanagers

76

Ich verstehe nicht, warum ich NamedTemporaryFile.name in diesem Beispiel nicht verspotten kann:

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)

Testergebnisse in:

AssertionError: <MagicMock name='NamedTemporaryFile().__enter__().name' id='140275675011280'> != 'abcde'
Willem
quelle

Antworten:

128

Sie setzen den falschen Mock: mock_tmpist nicht der Kontextmanager, sondern gibt einen Kontextmanager zurück. Ersetzen Sie Ihre Setup-Zeile durch:

mock_tmp.return_value.__enter__.return_value.name = mytmpname

und dein Test wird funktionieren.

Michele d'Amico
quelle
4

Erweiterung der Antwort von Peter K mit Pytest und dem Mocker Fixture.

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'
hmobrienv
quelle
3

Hier ist eine Alternative mit Pytest- und Mocker-Befestigung , die ebenfalls üblich ist:

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
Peter K.
quelle
0

Um Nathaniels Antwort zu erweitern, dieser Codeblock

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

macht effektiv drei Dinge

# 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

Wenn Sie durch tempfile.NamedTemporaryFileeine Instanz von Mockoder ersetzenMagicMock

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

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

# This will access mock_tmp.return_value.__enter__.return_value.name
return mytmp.name
FiddleStix
quelle