How to mock subprocess.run in pytest?
Asked Answered
B

2

9

I have the class InternalProc, defined as following:

class InternalProc:

    @staticmethod
    def get_data():
        try:
            result = subprocess.run(['bridge-client', '--business-credentials'],
                                    stdout=subprocess.PIPE)
            data = json.loads(result.stdout.decode('utf-8'))
            return data
        except Exception:
            logger.error("Unable to fetch the data")
            raise

I want to unit test the get_data(), how should I mock subprocess.run? I also want to assert on whether the exception was raised or not.

Bludgeon answered 23/2, 2021 at 11:11 Comment(1)
Does this answer your question? Mocking a subprocess call in PythonReuven
W
14

Seems like you need two tests to test this method, one that returns data and one that raises an Exception.

For the one where we return data we simply need to mock subprocess.run. Then we can create another Mock object to mock stdout.decode to return data that json.loads can parse. This means creating a Mock object for the behavior of stdout and then configuring our mocked subprocess.run to use this object.

For the other test we simply need to use the side_effect kwarg of the Mock object to raise an exception when it is called.

Given the following folder structure:

stackoverflow/
├── mypackage
│   ├── __init__.py
│   └── proc.py
└── tests
    ├── __init__.py
    └── test_proc.py

The tests we write are shown below.

from unittest.mock import MagicMock, patch

import pytest

from mypackage.proc import InternalProc


@patch("mypackage.proc.subprocess.run")
def test_get_data_valid(mock_run):
    mock_stdout = MagicMock()
    mock_stdout.configure_mock(
        **{
            "stdout.decode.return_value": '{"A": 3}'
        }
    )

    mock_run.return_value = mock_stdout

    result = InternalProc.get_data()
    assert result == {"A": 3}


@patch("mypackage.proc.subprocess.run", side_effect=Exception("foobar"))
def test_get_data_invalid(mock_run):
    with pytest.raises(Exception) as exc:
        InternalProc.get_data()
        assert "foobar" in str(exc.value)

Output:

======================================= test session starts ========================================
platform darwin -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: ***/stackoverflow
collected 2 items                                                                                  

tests/test_proc.py ..                                                                        [100%]

======================================== 2 passed in 0.07s =========================================

My suggestion to you, since I have seen you post multiple pytest/mock questions in the last few days is to spend some time reading the documentation for both. Make some toy examples and play around with the two packages. That is the only way you will learn how to mock and where to mock.

Workman answered 23/2, 2021 at 12:47 Comment(4)
I really appreciate your help, I have been reading the documentation but since I am relatively new to python and completely new to unit testing, it is giving me a hard time. It will be really really great if you can help me with few more questions if there are any.Bludgeon
In the second test, where are we raising the exception? As in where are we calling the get_Data()?Bludgeon
@Bludgeon I omitted a line accidentally in the second test, it should be updated now.Workman
Please help: stackoverflow.com/questions/66516824/mocking-exception-pytestBludgeon
C
0

Another solution is pytest-subprocess

Coopersmith answered 26/9, 2023 at 9:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.