0

I am using mock to test something I developed. In the app, I am using glob to loop for something in a directory for example: '/tmp/*.png'. It will collect all .png files in the directory and return a list of that files.

When I mock glob, it returns the calls. However it does not go well when used to loop in a for loop.

#stack.py
import os
import click
import hashlib
import glob

def bar(x):
    return os.path.basename(x)

def foo(path):
    images = glob.glob(path)
    for i in images:
        bar(i)


if __name__ == '__main__':
    foo()

#test_stack.py
import os
import unittest
import mock
import tempfile
import stack


class StackTest(unittest.TestCase):

    temp_dir = tempfile.gettempdir()
    temp_rg3 = os.path.join(temp_dir, "testfile.rg3")

    @mock.patch('stack.os')
    @mock.patch('stack.hashlib')
    @mock.patch('stack.glob')
    def test_stack(self, mock_glob, mock_hashlib, mock_os):
        stack.foo(self.temp_rg3)

        print(mock_glob.method_calls)
        print(mock_os.method_calls)

This is the return:

[call.glob('/tmp/testfile.rg3')]
[]
[]

After glob has been called in glob.glob(path) its return value does not reflect for images. Thus the for loop does not commence and bar(i) is not called, consequently mock_os returns no calls.

Nikko
  • 1,410
  • 1
  • 22
  • 49

1 Answers1

1

If I understood your question, it seems that you have not set a return value to your mock.

When you generate a MagicMock object, its default return value is the mock instance itself, as explained here. This instance is not an iterator and therefore won't do anything when iterated by a for loop.

You can provide the return values as below, changing your mock to also be the specific function you are calling:

@mock.patch('stack.os')
@mock.patch('stack.hashlib')
@mock.patch('stack.glob.glob', return_value=['a.png', 'b.png', 'c.png'])
def test_stack(self, mock_glob, mock_hashlib, mock_os):
    stack.foo(self.temp_rg3)

    print(mock_glob.method_calls)
    print(mock_os.method_calls)
  • What if there is two instance I called on glob.glob? How do I set different values for each? – Nikko Mar 24 '20 at 12:35
  • 1
    To set different values for distinct calls you can pass an iterator to the property `side_effect`. There is an example [here](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect). – Gabriel Conrado Mar 24 '20 at 14:46