Reputation: 91830
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:
subprocess.Popen
was called with the right values.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
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