Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a python constructor be mocked without mocking other properties of the object?

Tags:

Is it possible to mock a python constructor while continuing to use the production version other fields/functions on the same name? For example, given the production code:

class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")

and the following test code:

class FakeSubClass:
    def __init__(self) -> None:
        print("\nfake init called")


def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

we get the following output:

real sub init called

real sub sub init called

fake init called

Note that the last line MyClass.SubClass.SubSubClass() did not create a real SubSubClass because at this point it is an automatically created property of the SubClass mock.

My desired output is the following:

real sub init called

real sub sub init called

fake init called

real sub sub init called

In other words I want to mock ONLY the SubClass, but not SubSubClass. Things I have tried in place of the mocking line above (both of which do not work):

MyClass.SubClass.__init__ = Mock(side_effect=FakeSubClass.__init__)

MyClass.SubClass.__new__ = Mock(side_effect=FakeSubClass.__new__)

Note that I am aware of several ways the code could be refactored to avoid this issue but sadly the code cannot be refactored.

like image 380
tonicsoft Avatar asked Jun 27 '18 12:06

tonicsoft


People also ask

Can a constructor be mocked?

0, we can now mock Java constructors with Mockito. This allows us to return a mock from every object construction for testing purposes. Similar to mocking static method calls with Mockito, we can define the scope of when to return a mock from a Java constructor for a particular Java class.

Can you mock a module Python?

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest. mock provides a core Mock class removing the need to create a host of stubs throughout your test suite.

What is the difference between mock and MagicMock?

Mock vs. So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.


2 Answers

You can also fake the class, the thing MyClass.SubClass.SubSubClass() wont work in your case is that MyClass.SubClass is a Mock dont have SubSubClass definition. Simply let FakeSubClass inherit it from MyClass.SubClass will solve the issue.

And you can easily patch MyClass to FakeClass, and you will have the right test object rather than real object.

from unittest.mock import Mock


class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")


class FakeSubClass(MyClass.SubClass, Mock):
    def __init__(self) -> None:
        print("\nfake init called")


class FakeClass:
    class SubClass(FakeSubClass):
        pass


def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    FakeClass.SubClass()
    FakeClass.SubClass.SubSubClass()
like image 83
ZhouQuan Avatar answered Sep 17 '22 11:09

ZhouQuan


I agree that ZhouQuan has a very good answer, as it will work for any method or variable on MyClass.Subclass. That said, here are some variations that may or may not be useful.

If for whatever reason the FakeSubClass can not be direct edited, or you only want to inherit SubSubClass() but nothing else, it can be changed in test() like this.

def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    FakeSubClass.SubSubClass = MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

I think it may be worth noting that Mock does accept a wraps argument, which could be used to get similar behavior, though it is not exactly what you asked for. Here is an example.

from unittest.mock import Mock

class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")


class FakeSubClass:
    def __init__(self) -> None:
        print("\nfake init called")

def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass, wraps=MyClass.SubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

This gives a different output.

real sub init called

real sub sub init called

fake init called # A call to MyClass.SubClass() causes both real and fake inits.

real sub init called # Same MyClass.SubClass() call.

real sub sub init called # But now the SubSubClass() does resolve correctly.
like image 39
The Matt Avatar answered Sep 18 '22 11:09

The Matt