0

I have a script that uses subprocess.check_output of a command. There are instances where this command may fail and raise a subprocess.CalledProcessError.

I am attempting to write a unit test (using pytest to run the test) for this function and test the exception condition.

pytest 3.1.2
Python 3.5.3

I wrote this small bit of code to explore with to no avail.

# mytest.py
import subprocess

def test_sub():
    try:
        command_output = subprocess.check_output("unzip x".split())
        # unzip x is used to raise the exception when running with python
        # but would be replaced by proper command that may fail
        output = command_output
    except subprocess.CalledProcessError as cpe:
    #except Exception as cpe:
        print("\nType of error:", type(cpe))
        print(cpe.args)
        output = "unzip"
    return output

if __name__ == '__main__':
    print(test_sub())

When run with python mytest.py the output is unzip as expected since the unzip command will fail and raise the error.

This is the code to test the function

# test.py
import unittest
import unittest.mock as mock
from mytest import test_sub
import subprocess

class SubErrorTest(unittest.TestCase):

    @mock.patch('mytest.subprocess', autospec=True)
    def test_subprocess_error_thrown(self, mock_subprocess):

        mock_subprocess.check_output.side_effect = subprocess.CalledProcessError(returncode=2,cmd=["bad"])
        output = test_sub()
        self.assertEqual("unzip", output)

When run pytest test.py the test fails with the error

 output = test_sub() #error thrown from here in test

test.py:

 def test_sub():
   try:
       command_output = subprocess.check_output("unzip x".split())
       output = command_output
 except subprocess.CalledProcessError as cpe: #error is indicated here

E TypeError: catching classes that do not inherit from BaseException is not allowed

If I comment except subprocess.CalledProcessError as cpe: and uncomment #except Exception as cpe: the test passes with the ouptut:

test.py
Type of error: <class 'subprocess.CalledProcessError'>
()

This seems to suggest to me that the error is thrown as specified in the mock and caught and the exception block is executed. The question is then, why doesn't it work when catching subprocess.CalledProcessError.

This returns True

isinstance(subprocess.CalledProcessError(returncode=2,cmd=["bad"]), BaseException)

At this point I am guessing that there is something that I am missing in the whole process.

What am I missing?

Yuri L
  • 411
  • 5
  • 8
  • the solution is given here: https://stackoverflow.com/a/31873937/2082964 : you should not mock whole subprocess, but only part. If not your exception is also a mock. – Chris Maes Mar 19 '19 at 12:41

1 Answers1

2

I think you're patching the wrong path.

@mock.patch('mytest.subprocess', autospec=True)

The path should be the path to the module under test not the test itself. What I think is happening is your mocking subprocess in the test.py namespace so when you set the side_effect to subprocess.CalledProcessError you are setting it to a mock. You can verify this by logging the side_effect after you set it.

Dan
  • 1,874
  • 1
  • 16
  • 21