joedborg
joedborg

Reputation: 18353

Mocking subprocess.Popen without executing

I'm trying to write a unit test for a method that calls subprocess.Popen. All I want to test is that arg argument sent to Popen is as expected. I don't actually want Popen to run. Is this possible without mocking the arg list?

e.g.

def call_something(argument_list):
    binary = '/opt/mybin/'
    Popen([binary] + argument_list)

Then, to test.

@mock.patch('subprocess.Popen')
def test_call_something(self, mock_popen):
    binary = '/opt/mybin/'
    args = ['foo', 'bar']

    mock_popen.return_value.returncode = 0
    mock_popen.return_value.communicate.return_value = ('Running', '')

    call_something(args)

    self.assertEqual(
        [binary] + args,
        mock_popen.call_args_list
    )

The issues I'm getting here is that, first the binary is called (which I don't want) and second, call_args_list is empty.

Upvotes: 2

Views: 4100

Answers (1)

Romain Cata
Romain Cata

Reputation: 141

When using mock.patch you must point to the object where it is imported

See the documentation and this article that explains it well.


For example, in your case:

code.py

from subprocess import Popen

def call_something(argument_list):
    binary = '/opt/mybin/'
    Popen([binary] + argument_list)

test.py (Assuming both file are in the same folder, you need to patch code.Popen and not subprocess.Popen in the test)

from code import call_something

@mock.patch('code.Popen')
def test_call_something(self, mock_popen):
   binary = '/opt/mybin/'
   args = ['foo', 'bar']
   mock_popen.return_value.returncode = 0
   mock_popen.return_value.communicate.return_value = ('Running', '')

   call_something(args)

self.assertEqual(
    [binary] + args,
    mock_popen.call_args_list
)

Upvotes: 6

Related Questions