Python How to mock constructor with mockito for unit testing

eye-catch Python

A class constructor directly needs to be called in a class in some cases. It might not be a good design but sometimes it’s necessary to implement it in such a way. The unit tests are written well for the class and many steps are required to stub the function. In this case, it’s better to replace the constructor with our fake implementation.

Constructor is directly used in a function

The target code that we want to write tests is the following.

# item.py
class FolderGenerator:
    def generate(self, path):
        if len(path) < 3:
            raise ValueError("path is too short")
        if len(path) < 255:
            raise ValueError("path is too long")
        if len(path) == 5:
            return f"Folder was copied: [{path}]"

        return f"Folder was created: [{path}]"


class FileGenerator:
    def generate(self, path):
        return f"File was created: [{path}]"


# generator.py
from enum import Enum
from .item import FolderGenerator, FileGenerator

class FileType(Enum):
    file = 0
    folder = 1


class ItemGenerator:
    def execute(self, file_type: FileType, path: str):
        print("execute was called")
        if file_type == FileType.file:
            generator = FileGenerator()
        else:
            generator = FolderGenerator()

        result = generator.generate(path)

        if "copied" in result:
            return "copied"

        return result

Assume that the generate() function in FolderGenerator is more complicated. ItemGenerator handles it depending on the result. If we need to do a lot to return a specific value from FolderGenerator, the unit tests become not readable. That’s why we need to replace the constructor.

Import the whole test target module

The point to replace the constructor is to import the whole module of the test target. FolderGenerator is imported into the module. We need one higher level.

import pytest

from mockito import mock, when
from .generator import ItemGenerator, FileType
from . import generator as GE # This is the key


def test_execute_raise():
    instance = ItemGenerator()
    with pytest.raises(ValueError):
        instance.execute(FileType.folder, "ab")


def test_execute_mock_constructor():
    mock_instance = mock()
    when(GE).FolderGenerator(...).thenReturn(mock_instance)
    when(mock_instance).generate(...).thenReturn("fake value")

    instance = ItemGenerator()
    result = instance.execute(FileType.folder, "ab")
    assert result == "fake value"

def test_execute_mock_constructor2():
    mock_instance = mock()
    when(GE).FolderGenerator(...).thenReturn(mock_instance)
    when(mock_instance).generate(...).thenReturn("copied")

    instance = ItemGenerator()
    result = instance.execute(FileType.folder, "ab")
    assert result == "copied"

From GE, we can access FolderGenerator constructor; thus it can be stubbed by when. Then, we can return the desired value by the combination of when and thenReturn.

Check the following post if you are not familiar with mockito.

Comments

Copied title and URL