Last active
October 7, 2019 05:05
-
-
Save benburry/8905974 to your computer and use it in GitHub Desktop.
Python 2 - mock & unittest example for Popen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from subprocess import Popen, PIPE | |
def shell_out(command): | |
return Popen(command.split(' '), stdout=PIPE,stderr=PIPE).communicate()[0].strip('\n').split('\n') | |
def main(): | |
return shell_out('echo one\ntwo\nthree\n') | |
if __name__ == '__main__': | |
print main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import unittest | |
from echo import main | |
from mock import patch, Mock | |
NEW_OUTPUT = """ | |
eggs | |
cheese | |
shop | |
""" | |
# Mocking Popen directly - need to construct a Mock to return, and adjust its communicate() return_value | |
# The benefit of this approach is in not needing to do the strip/split on your fake return string | |
class MockPopen(unittest.TestCase): | |
# run before each test - the mock_popen will be available and in the right state in every test<something> function | |
def setUp(self): | |
# The "where to patch" bit is important - http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch | |
self.popen_patcher = patch('echo.Popen') | |
self.mock_popen = self.popen_patcher.start() | |
self.mock_rv = Mock() | |
# communicate() returns [STDOUT, STDERR] | |
self.mock_rv.communicate.return_value = [NEW_OUTPUT, None] | |
self.mock_popen.return_value = self.mock_rv | |
# run after each test | |
def tearDown(self): | |
self.popen_patcher.stop() | |
def testPopen(self): | |
output = main() | |
self.assertTrue(self.mock_popen.called) | |
self.assertTrue('eggs' in output) | |
self.assertEqual(3, len(output)) | |
def testMoon(self): | |
self.mock_rv.communicate.return_value[0] += 'moonlanguage' | |
output = main() | |
self.assertTrue(self.mock_popen.called) | |
self.assertTrue('moonlanguage' in output) | |
self.assertEqual(4, len(output)) | |
# I'd use this approach instead. | |
# Much easier (although you do need to mimic what shell_out is doing and convert your fake string into an array) | |
class MockShellOut(unittest.TestCase): | |
def setUp(self): | |
# The "where to patch" bit is important - http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch | |
self.shellout_patcher = patch('echo.shell_out') | |
self.mock_shellout = self.shellout_patcher.start() | |
# convert the fake return value into an array by calling split() | |
self.mock_shellout.return_value = NEW_OUTPUT.strip('\n').split('\n') | |
def tearDown(self): | |
self.shellout_patcher.stop() | |
def testShellOut(self): | |
output = main() | |
self.assertTrue(self.mock_shellout.called) | |
self.assertTrue('eggs' in output) | |
self.assertEqual(3, len(output)) | |
def testMoon(self): | |
self.mock_shellout.return_value += ['moonlanguage'] | |
output = main() | |
self.assertTrue(self.mock_shellout.called) | |
self.assertTrue('moonlanguage' in output) | |
self.assertEqual(4, len(output)) | |
if __name__ == '__main__': | |
# the unittest runner looks for any functions called test<something> in classes that extend TestCase | |
# so you can add as many test<something> functions to the classes above as you like, and they'll all get run | |
unittest.main() |
This has been quite useful for gaining more understanding of the mocking process. Thanks for that.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks, good tip.