Naftuli Kay
Naftuli Kay

Reputation: 91830

Mock an instance method with a certain return value?

I have a method under test that looks like this:

def execute_update(self):
    """Execute an update."""
    p = subprocess.Popen(['command'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    try:
        stdout, stderr = p.communicate()

        if p.returncode == 0:
            # successful update, notify
            self.logger.info("Successfully updated.")
        else:
            # failed update, notify
            self.logger.error("Unable to update (code {returncode}):\n{output}".format(
                returncode=p.returncode, output=stdout))

    except KeyboardInterrupt as e:
        # terminate the process
        p.terminate()
        raise e

I'm attempting to test its invocation of Popen and its invocation of the logging functions in a unittest.TestCase test method with mock:

@mock.patch.object(subprocess.Popen, 'communicate', autospec=True)
@mock.patch('updateservice.subprocess.Popen', autospec=True)
def test_fetch_metadata(self, mock_popen, mock_communicate):
    """Test that we can fetch metadata correctly."""
    mock_communicate.return_value = ("OUT", "ERR")
    mock_popen.returncode = 0

    self.reference.execute_update()

    # other asserts

The last line fails with:

    stdout, stderr = p.communicate()
ValueError: need more than 0 values to unpack

What am I doing wrong? I have the following requirements:

  1. Test that the constructor to subprocess.Popen was called with the right values.
  2. Test that the logging calls are running with the output and return code from the process.

Number two is easy enough, I'm just injecting a MagicMock as the logger object, but I"m having trouble with number one.

Upvotes: 2

Views: 3739

Answers (1)

idjaw
idjaw

Reputation: 26600

I think the main problem is coming from your patch object here:

@mock.patch.object(subprocess.Popen, 'communicate', autospec=True)

Oddly enough, it seems like the type of mock that is being created is:

<class 'unittest.mock.NonCallableMagicMock'>

This is the first time I have come across a NonCallableMagicMock type before, but looking at the minimal information I found on this, the docs specify this:

The part that raises a flag for me is here:

with the exception of return_value and side_effect which have no meaning on a non-callable mock.

It would require further investigation to determine what that exactly means. Taking that in to consideration, and maybe you have tried this already, the following change to your unittest yields successful mocking results:

@mock.patch('server.upd.subprocess.Popen', autospec=True)
def test_fetch_metadata(self, mock_popen):
    """Test that we can fetch metadata correctly."""

    mock_popen.return_value = Mock()
    mock_popen_obj = mock_popen.return_value

    mock_popen_obj.communicate.return_value = ("OUT", "ERR")
    mock_popen_obj.returncode = 0

    self.reference.execute_update()

So, as you can see, we are pretty much creating our mock object per the mock_popen.return_value. From there everything else is pretty much aligned with what you were doing.

Upvotes: 4

Related Questions