1

I'm trying to mock subprocess.check_output for some tests, but it doesn't seem to work. Consider this code:

from unittest.mock import patch
from subprocess import check_output

@patch('subprocess.check_output', return_value="not a date")
def test_mock_check_output(mock_check_output):
    result = check_output(['date'])
    print(result)  # should be "not a date", but is actually the system time
    mock_check_output.assert_called() # fails!

I would expect the check_output call in that test to go to the mock and not the real check_output, however it seems to go to the real one:

% pytest -s test_mock_check_output.py
============================= test session starts ==============================
platform darwin -- Python 3.8.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /Users/eaftan/test
plugins: mock-3.6.1, mock-server-0.1.0
collected 1 item                                                               

test_mock_check_output.py b'Thu Aug 19 16:51:16 PDT 2021\n'
F

=================================== FAILURES ===================================
____________________________ test_mock_check_output ____________________________

__wrapped_mock_method__ = <function NonCallableMock.assert_called at 0x105015ee0>
args = (<MagicMock name='check_output' id='4386427232'>,), kwargs = {}
__tracebackhide__ = True, msg = "Expected 'check_output' to have been called."
__mock_self = <MagicMock name='check_output' id='4386427232'>

    def assert_wrapper(
        __wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any
    ) -> None:
        __tracebackhide__ = True
        try:
>           __wrapped_mock_method__(*args, **kwargs)

../.pyenv/versions/3.8.9/lib/python3.8/site-packages/pytest_mock/plugin.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='check_output' id='4386427232'>

    def assert_called(self):
        """assert that the mock was called at least once
        """
        if self.call_count == 0:
            msg = ("Expected '%s' to have been called." %
                   (self._mock_name or 'mock'))
>           raise AssertionError(msg)
E           AssertionError: Expected 'check_output' to have been called.

../.pyenv/versions/3.8.9/lib/python3.8/unittest/mock.py:882: AssertionError

During handling of the above exception, another exception occurred:

mock_check_output = <MagicMock name='check_output' id='4386427232'>

    @patch('subprocess.check_output', return_value="not a date")
    def test_mock_check_output(mock_check_output):
        result = check_output(['date'])
        print(result)  # should be "not a date", but is actually the system time
>       mock_check_output.assert_called() # fails!
E       AssertionError: Expected 'check_output' to have been called.

test_mock_check_output.py:12: AssertionError
=========================== short test summary info ============================
FAILED test_mock_check_output.py::test_mock_check_output - AssertionError: Ex...
============================== 1 failed in 0.17s ===============================

Note the date output in the line test_mock_check_output.py b'Thu Aug 19 16:51:16 PDT 2021\n' and of course the assertion failure. What am I doing wrong?

Eddie Aftandilian
  • 219
  • 1
  • 2
  • 4
  • Try to change it to `@patch('.check_output', return_value="not a date")` – Brian Destura Aug 20 '21 at 00:24
  • Thanks @bdbd, that works. It seems the mocking library depends on the actual text used for the call? Doesn't that make it super brittle? I.e. if I refactor the code under test to change the way I'm importing symbols, I will break the mock? – Eddie Aftandilian Aug 23 '21 at 19:05
  • `the mocking library depends on the actual text used for the call?` -- Sorry I don't understand what this means. But essentially subprocess has already been loaded by your test file before you mocked it (that's why it's not working), so you need to mock your loaded subprocess instead of the main subprocess module – Brian Destura Aug 24 '21 at 00:28

0 Answers0