Skip to content

Different behavior with mock to same class or different class #125348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Fryguest opened this issue Oct 12, 2024 · 2 comments
Open

Different behavior with mock to same class or different class #125348

Fryguest opened this issue Oct 12, 2024 · 2 comments
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@Fryguest
Copy link

Fryguest commented Oct 12, 2024

Bug report

Bug description:

import unittest
from unittest.mock import patch

class Number:
    def __init__(self, value):
        print(f'init: {value}')
        self.value = value

    def get_value(self):
        return self.value

class MockNumber:
    def __init__(self, value):
        print(f'mock init: {value}')
        self.value = value

    def get_value(self):
        return self.value

class TestNumber(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_number(self):
        number = Number(1)
        with patch.object(Number, '__new__', return_value=number):
            test = Number(2)
            print(test.get_value())

        mock_number = MockNumber(1)
        with patch.object(Number, '__new__', return_value=mock_number):
            test = Number(2)
            print(test.get_value())

if __name__ == '__main__':
    unittest.main()

The output is

init: 1
init: 2
2
mock init: 1
1
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

When mock the function new of class Number to return an instance of MockNumber, the behavior is simple: just return the instance of MockNumber.
But when mock the function to return an instance of Number, the function of init will still be called.

CPython versions tested on:

3.11

Operating systems tested on:

Linux

@Fryguest Fryguest added the type-bug An unexpected behavior, bug, or error label Oct 12, 2024
@picnixz picnixz added the stdlib Python modules in the Lib dir label Oct 13, 2024
@ClanEver
Copy link

ClanEver commented Oct 25, 2024

I think this matches the expected behavior of __new__().

If __new__() is invoked during object construction and it returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to the object constructor.

The result is the same without using unittest.

Case 1
class Number:
    def __init__(self, value):
        print(f'init: {value}')
        self.value = value

num = Number(1)

def _new(cls, *args, **kwargs):
    return num

def _init(self, value):
    print(f'_init: {value}')

Number.__new__ = _new
Number.__init__ = _init
num2 = Number(2)
print(num2.value)

output:

init: 1
_init: 2
1
Case 2
class Number:
    def __init__(self, value):
        print(f'init: {value}')
        self.value = value

class MockNumber:
    def __init__(self, value):
        print(f'mock init: {value}')
        self.value = value

num = MockNumber(1)

def _new(cls, *args, **kwargs):
    return num

Number.__new__ = _new
num2 = Number(2)
print(num2.value)

output:

mock init: 1
1

@duaneg
Copy link
Contributor

duaneg commented Jun 3, 2025

Agreed this is the expected, documented behaviour. In particular, note the "and it returns an instance of cls" clause.

MockNumber is not a subclass of Number, so when a MockNumber is returned from Number.__new__ (with Number being the cls parameter) that clause is false and __init__ is not called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
Status: Todo
Development

No branches or pull requests

4 participants