Search code examples
pythonpython-3.xunit-testingmockingpython-unittest

Can't assert folder creation path with unittest patch


I'm trying to assert all paths that passed through os.makedirs to test that a folder structured has been created. The code isn't complicated and I'm sure it works find but my test reports that the method wasn't called with the paths I'm passing by checking calls in assert_any_call(my_path, 511). Now, what's weird in this case is that running assert_called_once as a hack to see the output shows that the method was actually called with the expected paths. What's going wrong in this case? I wouldn't like to leave this part of my code untested since another dev could work on it an might fall in the trap of double-checking it and waste his/her time.

Code

def generate_base_folders():
    if not os.path.exists(Path.cwd() / "unity_project"):
        raise FolderCreationException("unity_project folder doesn't exist in the project") 

    for category in ["characters", "environments", "ui", "cinematics"]:
        for tag in ["concept", "model", "animation", "vfx", "sfx", "vo", "music"]:
            os.makedirs(Path.cwd() / "sessions" / category / "common" / tag)
            os.makedirs(Path.cwd() / "unity_project" / "Assets" / category / "common" / tag)

Test

    @patch("os.makedirs")
    def test_base_folders(self, mkdirs_mock):
        assertions = []
        for category in ["characters", "environments", "ui", "cinematics"]:
            for tag in ["concept", "model", "animation", "vfx", "sfx", "vo", "music"]:
                assertions.append(Path.cwd() / "sessions" / category / "common" / tag)
                assertions.append(
                    Path.cwd() / "unity_project" / "Assets" / category / "common" / tag
                )
        sessions.generate_base_folders()
        for a in assertions:
            mkdirs_mock.assert_any_call(a, 511)

assert_any_call traceback

Fss.
======================================================================
FAIL: test_base_folders (sessions.tests.sessions_tests.TestSessionsSetup.test_base_folders)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\juank\AppData\Local\Programs\Python\Python313\Lib\unittest\mock.py", line 1424, in patched
    return func(*newargs, **newkeywargs)
  File "C:\Users\juank\dev\projects\python\gamedev_eco\sessions\tests\sessions_tests.py", line 40, in test_base_folders
    mkdirs_mock.assert_any_call(a, 511)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "C:\Users\juank\AppData\Local\Programs\Python\Python313\Lib\unittest\mock.py", line 1048, in assert_any_call
    raise AssertionError(
        '%s call not found' % expected_string
    ) from cause
AssertionError: makedirs(WindowsPath('C:/Users/juank/gamedev_eco/sessions/characters/common/concept'), 511) call not found

----------------------------------------------------------------------
Ran 4 tests in 0.018s

FAILED (failures=1, skipped=2)

assert_called_once traceback

Fss.
======================================================================
FAIL: test_base_folders (sessions.tests.sessions_tests.TestSessionsSetup.test_base_folders)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\juank\AppData\Local\Programs\Python\Python313\Lib\unittest\mock.py", line 1424, in patched
    return func(*newargs, **newkeywargs)
  File "C:\Users\juank\dev\projects\python\gamedev_eco\sessions\tests\sessions_tests.py", line 40, in test_base_folders
    mkdirs_mock.assert_called_once()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\juank\AppData\Local\Programs\Python\Python313\Lib\unittest\mock.py", line 956, in assert_called_once
    raise AssertionError(msg)
AssertionError: Expected 'makedirs' to have been called once. Called 56 times.
Calls: [call(WindowsPath('C:/Users/juank/gamedev_eco/sessions/characters/common/concept')),
(truncated the list for brevity)

Solution

  • There is a little, math, bug in your code!

    As your method has 2 for, nested, it need to execute the task of the second for for every item in this for (["concept", "model", "animation", "vfx", "sfx", "vo", "music"]), 7 items, as many times as has items in your first for (["characters", "environments", "ui", "cinematics"]), 4 items.

    It is a cartesian product, them 4 x 7 = 28. But you run the task of creation of dir twice

    os.makedirs(Path.cwd() / "sessions" / category / "common" / tag)
    os.makedirs(Path.cwd() / "unity_project" / "Assets" / category / "common" / tag)
    

    So, as 28 + 28 = 56, the test is right (the assertion is wrong)!

    Perhaps you want to test a more complete scenario, that may have more items or more contexts, but then you should review your code/tests.