Skip to content

Instantly share code, notes, and snippets.

@HorlogeSkynet
Last active February 17, 2019 09:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HorlogeSkynet/5b1378752669cce829d110fd48c4a5c8 to your computer and use it in GitHub Desktop.
Save HorlogeSkynet/5b1378752669cce829d110fd48c4a5c8 to your computer and use it in GitHub Desktop.
How to mock stdout runtime attribute of subprocess.Popen call in Python 3 ?
#!/usr/bin/env python3
import os
import tempfile
import unittest
from subprocess import PIPE, Popen, check_output
from unittest.mock import patch
class YourClass(object):
"""
This class contains a method in which we might want to mock the `Popen`
call and not the `check_output` one.
Nevertheless, the pipe uses a `.stdout` runtime attribute expecting a
file-like object, so we have to go trough a manual workaround to mock it.
Of course, you may want to do the same with `.stderr`, and it's possible ;)
"""
def __init__(self):
self.value = check_output(
['grep', '-E', 'regular|mocked'],
stdin=Popen(['echo', 'STDOUT (regular)'],
stdout=PIPE).stdout # The problem is here.
).decode().rstrip()
class PopenMock(unittest.TestCase):
def setUp(self):
# This temporary file will act as a standard stream pipe for `Popen`
self.stdout_mock = tempfile.NamedTemporaryFile(delete=False)
def tearDown(self):
# At the end of the test, we'll close and remove the created file
self.stdout_mock.close()
os.remove(self.stdout_mock.name)
@patch(
'__main__.Popen' # Please, take care of "patching" to the right place
)
def test(self, popen_mock):
# We store some data into the fake pipe here
self.stdout_mock.write(b'STDOUT (mocked)')
# We have to rewind to the beginning of the file for the next reading
self.stdout_mock.seek(0)
# The fake standard stream attribute is set here
popen_mock.return_value.stdout = self.stdout_mock
# Run your test(s) !
self.assertEqual(YourClass().value, 'STDOUT (mocked)')
if __name__ == '__main__':
unittest.main()
@sandeepkota
Copy link

In Python 2.7, its throwing the following error.

        process = Popen(stdout=PIPE, *popenargs, **kwargs)
>       output, unused_err = process.communicate()
E       ValueError: need more than 0 values to unpack

/usr/lib/python2.7/subprocess.py:567: ValueError

Could you give an example for 2.7?

@HorlogeSkynet
Copy link
Author

HorlogeSkynet commented Feb 17, 2019

Hey @sandeepkota,

As crazy as it may sound, GitHub has still not implemented a propoer Gists notifications engine, so your comment got lost... Sorry.

I've just tried to run this piece of code against Python2.7(.10), and with the following minor patch applied, it directly worked for me :

---  PopenMock_3.py	Sun Feb 17 10:39:14 2019
+++  PopenMock_2.py	Sun Feb 17 10:39:56 2019
@@ -1,11 +1,11 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python2
 
 
 import os
 import tempfile
 import unittest
 from subprocess import PIPE, Popen, check_output
-from unittest.mock import patch
+from mock import patch
 
 
 class YourClass(object):

Have you fixed this issue since ?
Do you want me to look at your problem precisely ?

With your snippet, it looks like it was related to the passed arguments of Popen here.

Bye 👋

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment