pythonpytestpytest-mock

how to mock a class during a unittest - in the factory method?


How can I mock the class "ClassA" in the test by replacing it completely If I have a class created through a factory method?

├── main.py
├── src
│   ├── core.py
│   ├── facade.py
│   ├── factory.py
│   └── something
│       ├── class_a.py
│       ├── class_b.py
│       ├── class_c.py
│       └── interface.py
└── tests
    └── test_core.py

class_a.py

from src.something.interface import Interface


class ClassA(Interface):

    def function(self):
        print('I\'m class A')

class_b.py

from src.something.interface import Interface


class ClassB(Interface):

    def function(self):
        print('I\'m class B')   

 

class_c.py

class ClassC:

    def function(self):
        print('I\'m class C')  

  

interface.py

from abc import ABC, abstractmethod


class Interface(ABC):

    @abstractmethod
    def function(self):
        pass

facade.py

from src.something.class_c import ClassC
from src.something.interface import Interface


class Facade:
    def __init__(self, interface: Interface, class_c: ClassC):
        self.function = interface
        self.class_c = class_c

    def functionClass(self):
        self.function.function()

    def functionAnotherClass(self):
        self.class_c.function()  

  

factory.py

from src.facade import Facade
from src.something.class_a import ClassA
from src.something.class_b import ClassB
from src.something.class_c import ClassC


class Factory:
    @staticmethod
    def build(config: str):
        if config == 'A':
            return Facade(ClassA(), ClassC())
        elif config == 'B':
            return Facade(ClassB(), ClassC())
        else:
            raise ValueError("Error factory")  


        
        

core.py

from src.factory import Factory


class Core:

    def __init__(self):
        self.__unit = Factory.build('A')

    def run(self):
        self.__unit.functionClass()
        self.__unit.functionAnotherClass()  

      

main.py

from src.core import Core

if __name__ == '__main__':
    core = Core()
    core.run()

test_core.py

import unittest
from unittest.mock import patch

from src.core import Core


class MockClassA:

    def function(self):
        print('I\'m mock class A')


class TestCore(unittest.TestCase):

    @patch('src.something.class_a.ClassA', new=MockClassA)
    def testOne(self):
        core = Core()
        core.run()
        pass

I expect that ClassA will be replaced with a mock class and the factory will send a locked object to the facade, however this does not happen when the test is run, it is output

OK

Process finished with exit code 0
I'm class A
I'm class C

Although I expect that it will be

Ran 1 test in 0.001s

OK

Process finished with exit code 0
I'm mock class A
I'm class C

can you tell me how to replace a class with such a code?


Solution

  • using 'src.factory.ClassA' instead of 'src.something.class_a.ClassA' or mocking the attribute of ClassA work, but the other one does not because the @patch does not change references of the class.

    class TestCore(unittest.TestCase):
    
        @patch('src.factory.ClassA', new=MockClassA)
        def testOne(self):
            core = Core()
            core.run()
            pass
    
        @patch('src.something.class_a.ClassA.function', lambda _: print('I\'m mock 2 class A'))
        def testTwo(self):
            core = Core()
            core.run()
            pass