简体   繁体   中英

python unit testing mocking Popen and Popen.communicate

I'm trying to write a python unittest that does a subprocess call, so I'd like to mock that call.

I've already gone through these SO questions (to no avail):

benchmark.py

from subprocess import Popen, PIPE, STDOUT

def some_func():

  with Popen(some_list, stdout=PIPE, stderr=STDOUT) as process:

    stdout, stderr = process.communicate(timeout=timeout)

test.py

import mock

@mock.patch('benchmark.Popen.communicate')
@mock.patch('benchmark.Popen')
def test_some_func(self, mock_popen, mock_comm):

  mock_popen.return_value = 0
  mock_comm.return_value = ('output', 'error')

  foo = benchmark.some_func()

When running the unittest I get:

    stdout, stderr  = process.communicate(timeout=timeout)
ValueError: not enough values to unpack (expected 2, got 0)

It looks like I'm not mocking the return value of communicate correctly; what am I doing wrong?

solution

I took the comments and suggested answers to solve things like this:

test.py

import mock

@mock.patch('benchmark.Popen')
def test_some_func(self, mock_popen):

  process = mock_popen.return_value.__enter__.return_value
  process.returncode = 0
  process.communicate.return_value = (b'some output', b'some error')

  foo = benchmark.some_func()

As jonrsharpe has mentioned, with Popen(...) as process is using Popen instance as a context manager, which calls __enter__ method and assign its value to process .

jonsharpe's solution uses return_value magic of mock and it works fine. But you can also implement a context manager and wrap the mocking logic in it:

import mock
import subprocess


class MockedPopen:

    def __init__(self, args, **kwargs):
        self.args = args
        self.returncode = 0

    def __enter__(self):
        return self

    def __exit__(self, exc_type, value, traceback):
        pass

    def communicate(self, input=None, timeout=None):
        if self.args[0] == 'ls':
            stdout = '\n'.join(['hello.txt', 'world.txt'])
            stderr = ''
            self.returncode = 0
        else:
            stdout = ''
            stderr = 'unknown command'
            self.returncode = 1

        return stdout, stderr


@mock.patch('subprocess.Popen', MockedPopen)
def foo():
    with subprocess.Popen(['ls']) as proc:
        stdout, stderr = proc.communicate()
        print(stdout, stderr)


foo()

Output:

hello.txt
world.txt

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM