Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking a subprocess call in Python

Tags:

python

mocking

I have a method (run_script) would like to test. Specifically I want to test that a call to subprocess.Popenoccurs. 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) 
like image 568
steve Avatar asked Sep 05 '14 19:09

steve


People also ask

What is subprocess call in Python?

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.

What is mocking 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.

What is the difference between subprocess run and call?

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.

What is subprocess stdout?

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.


2 Answers

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.

like image 90
PawelP Avatar answered Sep 23 '22 04:09

PawelP


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() 
like image 28
bergercookie Avatar answered Sep 26 '22 04:09

bergercookie