Issue
This Content is from Stack Overflow. Question asked by Thops
I’m utesting float(self) that is supposed to get downloadable file size from yt-dlp.
To call yt-dlp I’m using subprocess.check_output().
I’d like to mock it (subprocess.check_output) but can’t find the way how.
Please help.
Tried approaches:
- (mock check_output directly) How can I mock `subprocess.check_output`?
- (just mock Popen that is called by check_output) Mocking a subprocess call in Python
First one doesn’t work at all. It always reads 0.0.
Second one brings this traceback (Attribute Error):
Error
Traceback (most recent call last):
File "/usr/lib/python3.10/unittest/mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
File "/home/elphael/PycharmProjects/hoarder/core/test_file.py", line 61, in test__float__handlesSpecialCode
self.assertAlmostEqual(15.83, float(obj)) # verify it returns value
File "/home/elphael/PycharmProjects/hoarder/core/file.py", line 40, in __float__
bytesize = subprocess.check_output(f"yt-dlp -O filesize_approx {self.url}".split(),
File "/usr/lib/python3.10/subprocess.py", line 420, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/usr/lib/python3.10/subprocess.py", line 501, in run
with Popen(*popenargs, **kwargs) as process:
AttributeError: __enter__
I’ve tried to mock __enter __ but to no avail.
FUNCTION
- grab self.url and poll yt-dlp
- convert bytes to Mb (auxillary.bytesto())
- assign result to self.length and return
def __float__(self):
try:
bytesize = subprocess.check_output(f"yt-dlp -O filesize_approx {self.url}".split(),
stderr=subprocess.STDOUT)
megabytes = auxillary.bytesto(bytesize, 'm') # I only need approx size
self.length = round(megabytes, 2)
except subprocess.CalledProcessError:
return -2.0
return self.length
UTEST
- init a class that calls float(self)
- verify self.length = expected
@patch("subprocess.Popen")
def test__float__handlesSpecialCode(self, mock_subproc_popen):
"""
-1.0 length is meant to show that length is yet to be calculated
"""
process_mock = Mock()
attrs = {"communicate.return_value": ("output", "error")}
process_mock.configure_mock(**attrs)
mock_subproc_popen.return_value = process_mock
obj = TFile("https://www.youtube.com/watch?v=GtL1huin9EE", -1.0)
self.assertAlmostEqual(15.83, float(obj)) # verify it returns value
self.assertNotEqual(-1, obj.length) # verify self.length preserves that value
Any input is deeply appreciated.
Solution
It seems unusual to me that you use the patch decorator over the run_script
function, since you don’t pass a mock argument there.
How about this:
from unittest import mock
import subprocess
def run_script(file_path):
process = subprocess.Popen(["myscript", -M, file_path], stdout=subprocess.PIPE)
output, err = process.communicate()
return process.returncode
@mock.patch("subprocess.Popen")
def test_run_script(self, mock_subproc_popen):
process_mock = mock.Mock()
attrs = {"communicate.return_value": ("output", "error")}
process_mock.configure_mock(**attrs)
mock_subproc_popen.return_value = process_mock
am.account_manager("path") # this calls run_script somewhere, is that right?
self.assertTrue(mock_subproc_popen.called)
Right now, your mocked subprocess.Popen seems to return a tuple, causeing process.communicate() to raise TypeError: 'tuple' object is not callable.
. Therefore it’s most important to get the return_value on mock_subproc_popen just right.
This Question was asked in StackOverflow by steve and Answered by PawelP It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.