[issue39578] MagicMock specialisation instance can no longer be passed to new MagicMock instance

Frank Harrison report at bugs.python.org
Fri Feb 7 04:38:41 EST 2020


New submission from Frank Harrison <frank at doublethefish.com>:

This is my first bug logged here, I've tried to follow the guideline and search for this issue; please let me know if I missed anything.

Summary:
unittest.mock.MagicMock has a regression starting in 3.8. The regression was only tested on latest non-prerelease versions of python 3.5, 3.6, 3.7, 3.8 and 3.9. Tested on OSX and Fedora 31.

Repro:
------
If you create an instance of a MagicMock specialisation with parameters to __init__(),  you can no longer pass that instance to the __init__() function of another MagicMock object e.g. a base-class is replaced with MagicMock. See the unittests bellow for more details, use-cases and fail situations.

What happens:
-------------
Here's a python3.9 example traceback. It may be worth noting that there is a difference in the tracebacks between 3.8 and 3.9.

Traceback (most recent call last):
  File "<...>", line <..>, in test_raw_magic_moc_passing_thru_single_pos
    mock_object = mock.MagicMock(mock_param)  # error is here, instantiating another object
  File "/usr/lib64/python3.9/unittest/mock.py", line 408, in __new__
    if spec_arg and _is_async_obj(spec_arg):
  File "/usr/lib64/python3.9/unittest/mock.py", line 2119, in __get__
    return self.create_mock()
  File "/usr/lib64/python3.9/unittest/mock.py", line 2112, in create_mock
    m = parent._get_child_mock(name=entry, _new_name=entry,
  File "/usr/lib64/python3.9/unittest/mock.py", line 1014, in _get_child_mock
    return klass(**kw)
TypeError: __init__() got an unexpected keyword argument 'name'


Code demonstrating the problem:
-------------------------------

import unittest

from unittest import mock


class TestMockMagicAssociativeHierarchies(unittest.TestCase):
    """ Mimicing real-world testing where we mock a base-class

    The intent here is to demonstrate some of the requirements of associative-
    hierarchies e.g. where a class may have its associative-parent set at
    run-time, rather that defining it via a class-hierarchy. Obviously this
    also needs to work with class-hierarchies, that is an associative-parent is
    likely to be a specialisation of some interface, usually one that is being
    mocked.

    For example tkinter and Qt have both a class-hierarchy and a layout-
    hierarchy; the latter is modifyable at runtime.

    Most of the tests here mimic a specialisation of an upstream object (say a
    tk.Frame class), instantiating that specialisation and then passing it to
    another object. The reason behind this is an observed regression in Python
    3.8.
    """
    def test_raw_magic_moc_passing_thru_no_params(self):
        """ REGRESSION: Python3.8 (inc Python3.9)

        Create a mocked specialisation passing it to another mock.

        One real-world use-case for this is simple cases where we simply want to
        define a new convenience type that forces a default configuration of
        the inherited type (calls super().__init__()).
        """
        class MockSubCallsParentInit(mock.MagicMock):
            def __init__(self):
                super().__init__()  # intentionally empty
        mock_param = MockSubCallsParentInit()
        mock_object = mock.MagicMock(mock_param)  # error is here, instantiating another object
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_raw_magic_moc_passing_thru_single_pos(self):
        """ REGRESSION: Python3.8 (inc Python3.9)

        Same as test_raw_magic_moc_no_init_params() but we want to specialise
        with positional arguments. """
        class MockSubCallsParentInitWithPositionalParam(mock.MagicMock):
            def __init__(self):
                super().__init__("specialise init calls")
        mock_param = MockSubCallsParentInitWithPositionalParam()
        mock_object = mock.MagicMock(mock_param)  # error is here, instantiating another object
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_raw_magic_moc_passing_thru_single_kwarg(self):
        """ REGRESSION: Python3.8 (inc Python3.9)

        Same as test_raw_magic_moc_passing_thru_single_pos() but we want to
        specialise with a key-word argument. """
        class MockSubCallsParentInitWithPositionalParam(mock.MagicMock):
            def __init__(self):
                super().__init__(__some_key_word__="some data")
        mock_param = MockSubCallsParentInitWithPositionalParam()
        mock_object = mock.MagicMock(mock_param)  # error is here, instantiating another object
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_mock_as_param_no_inheritance(self):
        """ PASSES 

        Mimic mocking out types, without type specialisation.
        for example in pseudo code 
            tk.Frame = mock.MagicMock; tk.Frame(t.Frame) """
        mock_param = mock.MagicMock()
        mock_object = mock.MagicMock(mock_param)
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_mock_as_param_no_init_override(self):
        """ PASSES 

        Leaves the __init__() function behaviour as default; should always
        work. Note that we do not specialise member functions.

        Although the intent here is similar to the one captured by
        test_raw_magic_moc_passing_thru_no_params(), this is a less likely
        usecase, although it does happen, but is here for completeness """
        class MockSub(mock.MagicMock): pass
        mock_param = MockSub()
        mock_object = mock.MagicMock(mock_param)
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_init_with_args_n_kwargs_passthru(self):
        """ PASSES

        Intended to be the same as test_mock_as_param_no_init_override as well
        as a base-test for ithe usecases where a user will define more complex
        behaviours such as key-word modification, member-variable definitions
        and so on. """
        class MockSubInitPassThruArgsNKwargs(mock.MagicMock):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)  # intentionally redundant
        mock_param = MockSubInitPassThruArgsNKwargs()
        mock_object = mock.MagicMock(mock_param)
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_init_with_args_n_kwargs_modify_kwargs(self):
        """ PASSES

        Same as test_init_with_args_n_kwargs_passthru() but modifies the kwargs
        dict on the way through the __init__() function.
        """
        class MockSubModifyKwargs(mock.MagicMock):
            def __init__(self, *args, **kwargs):
                kwargs["__kw args added__"] = "test value"
                super().__init__(*args, **kwargs)
        mock_param = MockSubModifyKwargs()
        mock_object = mock.MagicMock(mock_param)
        self.assertIsInstance(mock_object, mock.MagicMock)

    def test_init_with_args_n_kwargs_modify_args(self):
        """ PASSES

        Same as test_init_with_args_n_kwargs_passthru() but modifies the args
        on their way through the __init__() function.
        """
        class MockSubModifyArgs(mock.MagicMock):
            def __init__(self, *args, **kwargs):
                super().__init__("test value", *args, **kwargs)
        mock_param = MockSubModifyArgs()
        mock_object = mock.MagicMock(mock_param)
        self.assertIsInstance(mock_object, mock.MagicMock)

----------
components: Library (Lib)
messages: 361552
nosy: Frank Harrison
priority: normal
severity: normal
status: open
title: MagicMock specialisation instance can no longer be passed to new MagicMock instance
versions: Python 3.8, Python 3.9

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue39578>
_______________________________________


More information about the Python-bugs-list mailing list