Home > front end >  How to properly mock subprocess.check_output()?
How to properly mock subprocess.check_output()?

Time:09-20

I'm utesting float(self) that is supposed to get downloadable file size from yt-dlp.

To call yt-dlp, subprocess.check_output() is being used. I'd like to mock it but can't find the way how.

Please help.

Tried approaches:

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

  1. grab self.url and poll yt-dlp
  2. convert bytes to Mb (auxillary.bytesto())
  3. 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

  1. init a class that calls float(self)
  2. 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.

CodePudding user response:

I would recommend you to avoid the mock and have another method of your class to calculate the bytesize. In your test, you use a child class of this class and override this function.

For example:

   def __float__(self):

        try:
            bytesize = self.calculate_bytesize()
            megabytes = auxillary.bytesto(bytesize, 'm')  # I only need approx size
            self.length = round(megabytes, 2)
        except subprocess.CalledProcessError:
            return -2.0
        return self.length

    def calculate_bytesize():
        return subprocess.check_output(f"yt-dlp -O filesize_approx {self.url}".split(), stderr=subprocess.STDOUT)

and in your test

def test__float__handlesSpecialCode(self, mock_subproc_popen):
    """
        -1.0 length is meant to show that length is yet to be calculated
    """
    obj = DummyTFile("https://www.youtube.com/watch?v=GtL1huin9EE", -1.0)
    obj.mock_bytesize = ("output", "error")

    self.assertAlmostEqual(15.83, float(obj))  # verify it returns value
    self.assertNotEqual(-1, obj.length)

class DummyTFile(TFile):
    def __init__(self, *args):
        super().__init__(*args)
        self.mock_bytesize = None

    def calculate_bytesize():
        return self.mock_bytesize

Would that be a feasable approach for you?

  • Related