I have a method (run_script
) would like to test. Specifically I want to test that a call to subprocess.Popen
occurs. It would be even better to test that subprocess.Popen
is called with certain parameters. When I run the test however I get TypeError: 'tuple' object is not callable
.
How can I test my method to ensure that subprocess is actually being called using mocks?
@mock.patch('subprocess.Popen') def run_script(file_path): process = subprocess.Popen(['myscript', -M, file_path], stdout=subprocess.PIPE) output,err = process.communicate() return process.returncode def test_run_script(self, mock_subproc_popen): mock_subproc_popen.return_value = mock.Mock(communicate=('ouput','error'), returncode=0) am.account_manager("path") self.assertTrue(mock_subproc_popen.called)
Subprocess in Python is a module used to run new codes and applications by creating new processes. It lets you start new applications right from the Python program you are currently writing. So, if you want to run external programs from a git repository or codes from C or C++ programs, you can use subprocess in Python.
Mocking is simply the act of replacing the part of the application you are testing with a dummy version of that part called a mock. Instead of calling the actual implementation, you would call the mock, and then make assertions about what you expect to happen.
I can say that you use subprocess. call() when you want the program to wait for the process to complete before moving onto the next process. In the case of subprocess. run() , the program will attempt to run all the processes at once, inevitably causing the program to crash.
When subprocess. STDOUT is specified, the subprocess's standard error stream will be connected to the same pipe as the standard output stream. All other keyword arguments are passed to subprocess. Popen without interpretation, except for bufsize, universal_newlines and shell, which should not be specified at all.
It seems unusual to me that you use the patch decorator over the run_script
function, since you don't pass a mock argument there.
How about this:
def run_script(file_path): process = subprocess.Popen(['myscript', -M, file_path], stdout=subprocess.PIPE) output,err = process.communicate() return process.returncode @mock.patch('subprocess.Popen') def test_run_script(self, mock_subproc_popen): process_mock = mock.Mock() attrs = {'communicate.return_value': ('output', 'error')} process_mock.configure_mock(**attrs) mock_subproc_popen.return_value = process_mock am.account_manager("path") # this calls run_script somewhere, is that right? self.assertTrue(mock_subproc_popen.called)
Right now, your mocked subprocess.Popen seems to return a tuple, causeing process.communicate() to raise TypeError: 'tuple' object is not callable.
. Therefore it's most important to get the return_value on mock_subproc_popen just right.
There exists the testfixtures package that you can make use of.
Here's an example on using the mock Popen:
from unittest import TestCase from testfixtures.mock import call from testfixtures import Replacer, ShouldRaise, compare from testfixtures.popen import MockPopen, PopenBehaviour class TestMyFunc(TestCase): def setUp(self): self.Popen = MockPopen() self.r = Replacer() self.r.replace(dotted_path, self.Popen) self.addCleanup(self.r.restore) def test_example(self): # set up self.Popen.set_command('svn ls -R foo', stdout=b'o', stderr=b'e') # testing of results compare(my_func(), b'o') # testing calls were in the right order and with the correct parameters: process = call.Popen(['svn', 'ls', '-R', 'foo'], stderr=PIPE, stdout=PIPE) compare(Popen.all_calls, expected=[ process, process.communicate() ]) def test_example_bad_returncode(self): # set up Popen.set_command('svn ls -R foo', stdout=b'o', stderr=b'e', returncode=1) # testing of error with ShouldRaise(RuntimeError('something bad happened')): my_func()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With