Skip to content
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

Class' __hash__ not being used in MagicMock subclass #150

Open
Galarzaa90 opened this issue Jun 5, 2020 · 0 comments
Open

Class' __hash__ not being used in MagicMock subclass #150

Galarzaa90 opened this issue Jun 5, 2020 · 0 comments

Comments

@Galarzaa90
Copy link

Galarzaa90 commented Jun 5, 2020

I have a series of Mock classes for my testing module, based on this:
https://github.com/python-discord/bot/blob/master/tests/helpers.py
But I'm using asynctest because I'm using Python 3.6

Base classes:

class CustomMockMixin:
    discord_id = itertools.count(0)
    spec_set = None

    def __init__(self, *args, **kwargs):
        name = kwargs.pop('name', None)  # `name` has special meaning for Mock classes, so we need to set it manually.
        super().__init__(*args, spec_set=self.spec_set, **kwargs)

        if name:
            self.name = name

    def _get_child_mock(self, *args, **kwargs):
        """This method by default returns an instance of the same class for any attribute or method of the class.

        This would cause MockBot, for instance, to return another instance of MockBot when using ``bot.get_guild``.

        This overwrites the original logic to return MagicMock objects by default, and CoroutineMocks for names defined
        in ``_spec_coroutines``"""
        _new_name = kwargs.get("_new_name")
        if _new_name in self.__dict__['_spec_coroutines']:
            return asynctest.CoroutineMock(*args, **kwargs)

        _type = type(self)

        if issubclass(_type, asynctest.MagicMock) and _new_name in asynctest.mock.async_magic_coroutines:
            klass = asynctest.CoroutineMock
        elif issubclass(_type, asynctest.CoroutineMock):
            klass = asynctest.MagicMock
        elif not issubclass(_type, unittest.mock.CallableMixin):
            # noinspection PyTypeHints
            if issubclass(_type, unittest.mock.NonCallableMagicMock):
                klass = asynctest.MagicMock
            elif issubclass(_type, asynctest.NonCallableMock):
                klass = asynctest.Mock
        else:
            klass = asynctest.MagicMock

        # noinspection PyUnboundLocalVariable
        return klass(*args, **kwargs)


class HashableMixin(discord.mixins.EqualityComparable):
    """
    Mixin that provides similar hashing and equality functionality as discord.py's `Hashable` mixin.
    Note: discord.py`s `Hashable` mixin bit-shifts `self.id` (`>> 22`); to prevent hash-collisions
    for the relative small `id` integers we generally use in tests, this bit-shift is omitted.
    """

    def __hash__(self):
        return self.id

And this is the specific class I'm struggling with:

class MockRole(CustomMockMixin, asynctest.MagicMock, ColourMixin, HashableMixin):
    spec_set = discord.Role

    def __init__(self, *args, **kwargs):
        default_kwargs = {
            'id': next(self.discord_id),
            'name': 'role',
            'position': 1,
            'colour': discord.Colour(0xdeadbf),
            'permissions': discord.Permissions(),
        }
        super().__init__(*args, **collections.ChainMap(kwargs, default_kwargs))

        if 'mention' not in kwargs:
            self.mention = f'&{self.name}'

    def __str__(self):
        return f"<{self.mention}>"

Needless to say, I'm over 100 test cases in with this, and I had already noticed that __str__ was not working as expected, but now I noticed that __hash__ isn't, and this affects my tests' equality checks.

If I call the methods explicitly stating the class, they work (even calling the parent's classes methods)

>>> role = MockRole(name="Premium", id=3)
>>> str(role )
'<MockRole spec_set=\'Role\' id=\'2508777936776\'>'
>>> MockRole.__str__(role )
'<&Premium>'
>>> hash(role )
-9223371880056154760
>>> MockRole.__hash__(role )
5

I'm not sure if this is part of my implementation or part of the library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant