aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_helpers.py
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2019-10-14 22:13:22 +0200
committerGravatar Sebastiaan Zeeff <[email protected]>2019-10-14 22:13:22 +0200
commite4e01cd5388da19435637353e711c2feab5a0e59 (patch)
tree7284fdb417e8b1e42ea55a9d8d48c20f933dc9c3 /tests/test_helpers.py
parentRemove empty tests.cogs folder (diff)
Add more specialized Mocks to tests.helpers
This commit introduces some new Mock-types to the already existing Mock-types for discord.py objects. The total list is now: - MockGuild - MockRole - MockMember - MockBot - MockContext - MockTextChannel - MockMessage In addition, I've added all coroutines in the documentation for these discord.py objects as `AsyncMock` attributes to ease testing. Tests ensure that the attributes set for the Mocks exist for the actual discord.py objects as well.
Diffstat (limited to 'tests/test_helpers.py')
-rw-r--r--tests/test_helpers.py385
1 files changed, 206 insertions, 179 deletions
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index 766fe17b8..f08239981 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -8,114 +8,8 @@ import discord
from tests import helpers
-class MockObjectTests(unittest.TestCase):
- """Tests the mock objects and mixins we've defined."""
- @classmethod
- def setUpClass(cls):
- cls.hashable_mocks = (helpers.MockRole, helpers.MockMember, helpers.MockGuild)
-
- def test_colour_mixin(self):
- """Test if the ColourMixin adds aliasing of color -> colour for child classes."""
- class MockHemlock(unittest.mock.MagicMock, helpers.ColourMixin):
- pass
-
- hemlock = MockHemlock()
- hemlock.color = 1
- self.assertEqual(hemlock.colour, 1)
- self.assertEqual(hemlock.colour, hemlock.color)
-
- def test_hashable_mixin_hash_returns_id(self):
- """Test if the HashableMixing uses the id attribute for hashing."""
- class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
- pass
-
- scragly = MockScragly()
- scragly.id = 10
- self.assertEqual(hash(scragly), scragly.id)
-
- def test_hashable_mixin_uses_id_for_equality_comparison(self):
- """Test if the HashableMixing uses the id attribute for hashing."""
- class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
- pass
-
- scragly = MockScragly(spec=object)
- scragly.id = 10
- eevee = MockScragly(spec=object)
- eevee.id = 10
- python = MockScragly(spec=object)
- python.id = 20
-
- self.assertTrue(scragly == eevee)
- self.assertFalse(scragly == python)
-
- def test_hashable_mixin_uses_id_for_nonequality_comparison(self):
- """Test if the HashableMixing uses the id attribute for hashing."""
- class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
- pass
-
- scragly = MockScragly(spec=object)
- scragly.id = 10
- eevee = MockScragly(spec=object)
- eevee.id = 10
- python = MockScragly(spec=object)
- python.id = 20
-
- self.assertTrue(scragly != python)
- self.assertFalse(scragly != eevee)
-
- def test_mock_class_with_hashable_mixin_uses_id_for_hashing(self):
- """Test if the MagicMock subclasses that implement the HashableMixin use id for hash."""
- for mock in self.hashable_mocks:
- with self.subTest(mock_class=mock):
- instance = helpers.MockRole(role_id=100)
- self.assertEqual(hash(instance), instance.id)
-
- def test_mock_class_with_hashable_mixin_uses_id_for_equality(self):
- """Test if MagicMocks that implement the HashableMixin use id for equality comparisons."""
- for mock_class in self.hashable_mocks:
- with self.subTest(mock_class=mock_class):
- instance_one = mock_class()
- instance_two = mock_class()
- instance_three = mock_class()
-
- instance_one.id = 10
- instance_two.id = 10
- instance_three.id = 20
-
- self.assertTrue(instance_one == instance_two)
- self.assertFalse(instance_one == instance_three)
-
- def test_mock_class_with_hashable_mixin_uses_id_for_nonequality(self):
- """Test if MagicMocks that implement HashableMixin use id for nonequality comparisons."""
- for mock_class in self.hashable_mocks:
- with self.subTest(mock_class=mock_class):
- instance_one = mock_class()
- instance_two = mock_class()
- instance_three = mock_class()
-
- instance_one.id = 10
- instance_two.id = 10
- instance_three.id = 20
-
- self.assertFalse(instance_one != instance_two)
- self.assertTrue(instance_one != instance_three)
-
- def test_spec_propagation_of_mock_subclasses(self):
- """Test if the `spec` does not propagate to attributes of the mock object."""
- test_values = (
- (helpers.MockGuild, "region"),
- (helpers.MockRole, "mentionable"),
- (helpers.MockMember, "display_name"),
- (helpers.MockBot, "owner_id"),
- (helpers.MockContext, "command_failed"),
- )
-
- for mock_type, valid_attribute in test_values:
- with self.subTest(mock_type=mock_type, attribute=valid_attribute):
- mock = mock_type()
- self.assertTrue(isinstance(mock, mock_type))
- attribute = getattr(mock, valid_attribute)
- self.assertTrue(isinstance(attribute, mock_type.attribute_mocktype))
+class DiscordMocksTests(unittest.TestCase):
+ """Tests for our specialized discord.py mocks."""
def test_mock_role_default_initialization(self):
"""Test if the default initialization of MockRole results in the correct object."""
@@ -152,28 +46,6 @@ class MockObjectTests(unittest.TestCase):
self.assertEqual(role.guild, "Dino Man")
self.assertTrue(role.hoist)
- def test_mock_role_rejects_accessing_attributes_not_following_spec(self):
- """Test if MockRole throws AttributeError for attribute not existing in discord.Role."""
- with self.assertRaises(AttributeError):
- role = helpers.MockRole()
- role.joseph
-
- def test_mock_role_rejects_accessing_methods_not_following_spec(self):
- """Test if MockRole throws AttributeError for method not existing in discord.Role."""
- with self.assertRaises(AttributeError):
- role = helpers.MockRole()
- role.lemon()
-
- def test_mock_role_accepts_accessing_attributes_following_spec(self):
- """Test if MockRole accepts attribute access for valid attributes of discord.Role."""
- role = helpers.MockRole()
- role.hoist
-
- def test_mock_role_accepts_accessing_methods_following_spec(self):
- """Test if MockRole accepts method calls for valid methods of discord.Role."""
- role = helpers.MockRole()
- role.edit()
-
def test_mock_role_uses_position_for_less_than_greater_than(self):
"""Test if `<` and `>` comparisons for MockRole are based on its position attribute."""
role_one = helpers.MockRole(position=1)
@@ -223,28 +95,6 @@ class MockObjectTests(unittest.TestCase):
self.assertEqual(member.nick, "Dino Man")
self.assertEqual(member.colour, discord.Colour.default())
- def test_mock_member_rejects_accessing_attributes_not_following_spec(self):
- """Test if MockMember throws AttributeError for attribute not existing in spec discord.Member."""
- with self.assertRaises(AttributeError):
- member = helpers.MockMember()
- member.joseph
-
- def test_mock_member_rejects_accessing_methods_not_following_spec(self):
- """Test if MockMember throws AttributeError for method not existing in spec discord.Member."""
- with self.assertRaises(AttributeError):
- member = helpers.MockMember()
- member.lemon()
-
- def test_mock_member_accepts_accessing_attributes_following_spec(self):
- """Test if MockMember accepts attribute access for valid attributes of discord.Member."""
- member = helpers.MockMember()
- member.display_name
-
- def test_mock_member_accepts_accessing_methods_following_spec(self):
- """Test if MockMember accepts method calls for valid methods of discord.Member."""
- member = helpers.MockMember()
- member.mentioned_in()
-
def test_mock_guild_default_initialization(self):
"""Test if the default initialization of Mockguild results in the correct object."""
guild = helpers.MockGuild()
@@ -276,28 +126,6 @@ class MockObjectTests(unittest.TestCase):
self.assertTupleEqual(guild.emojis, (":hyperjoseph:", ":pensive_ela:"))
self.assertEqual(guild.premium_subscription_count, 15)
- def test_mock_guild_rejects_accessing_attributes_not_following_spec(self):
- """Test if MockGuild throws AttributeError for attribute not existing in spec discord.Guild."""
- with self.assertRaises(AttributeError):
- guild = helpers.MockGuild()
- guild.aperture
-
- def test_mock_guild_rejects_accessing_methods_not_following_spec(self):
- """Test if MockGuild throws AttributeError for method not existing in spec discord.Guild."""
- with self.assertRaises(AttributeError):
- guild = helpers.MockGuild()
- guild.volcyyy()
-
- def test_mock_guild_accepts_accessing_attributes_following_spec(self):
- """Test if MockGuild accepts attribute access for valid attributes of discord.Guild."""
- guild = helpers.MockGuild()
- guild.name
-
- def test_mock_guild_accepts_accessing_methods_following_spec(self):
- """Test if MockGuild accepts method calls for valid methods of discord.Guild."""
- guild = helpers.MockGuild()
- guild.by_category()
-
def test_mock_bot_default_initialization(self):
"""Tests if MockBot initializes with the correct values."""
bot = helpers.MockBot()
@@ -305,10 +133,6 @@ class MockObjectTests(unittest.TestCase):
# The `spec` argument makes sure `isistance` checks with `discord.ext.commands.Bot` pass
self.assertIsInstance(bot, discord.ext.commands.Bot)
- self.assertIsInstance(bot._before_invoke, helpers.AsyncMock)
- self.assertIsInstance(bot._after_invoke, helpers.AsyncMock)
- self.assertEqual(bot.user, helpers.MockMember(name="Python", user_id=123456789))
-
def test_mock_context_default_initialization(self):
"""Tests if MockContext initializes with the correct values."""
context = helpers.MockContext()
@@ -317,10 +141,213 @@ class MockObjectTests(unittest.TestCase):
self.assertIsInstance(context, discord.ext.commands.Context)
self.assertIsInstance(context.bot, helpers.MockBot)
- self.assertIsInstance(context.send, helpers.AsyncMock)
self.assertIsInstance(context.guild, helpers.MockGuild)
self.assertIsInstance(context.author, helpers.MockMember)
+ def test_mocks_allows_access_to_attributes_part_of_spec(self):
+ """Accessing attributes that are valid for the objects they mock should succeed."""
+ mocks = (
+ (helpers.MockGuild(), 'name'),
+ (helpers.MockRole(), 'hoist'),
+ (helpers.MockMember(), 'display_name'),
+ (helpers.MockBot(), 'user'),
+ (helpers.MockContext(), 'invoked_with'),
+ (helpers.MockTextChannel(), 'last_message'),
+ (helpers.MockMessage(), 'mention_everyone'),
+ )
+
+ for mock, valid_attribute in mocks:
+ with self.subTest(mock=mock):
+ try:
+ getattr(mock, valid_attribute)
+ except AttributeError:
+ msg = f"accessing valid attribute `{valid_attribute}` raised an AttributeError"
+ self.fail(msg)
+
+ @unittest.mock.patch(f'{__name__}.DiscordMocksTests.subTest')
+ @unittest.mock.patch(f'{__name__}.getattr')
+ def test_mock_allows_access_to_attributes_test(self, mock_getattr, mock_subtest):
+ """The valid attribute test should raise an AssertionError after an AttributeError."""
+ mock_getattr.side_effect = AttributeError
+
+ msg = "accessing valid attribute `name` raised an AttributeError"
+ with self.assertRaises(AssertionError, msg=msg):
+ self.test_mocks_allows_access_to_attributes_part_of_spec()
+
+ def test_mocks_rejects_access_to_attributes_not_part_of_spec(self):
+ """Accessing attributes that are invalid for the objects they mock should fail."""
+ mocks = (
+ helpers.MockGuild(),
+ helpers.MockRole(),
+ helpers.MockMember(),
+ helpers.MockBot(),
+ helpers.MockContext(),
+ helpers.MockTextChannel(),
+ helpers.MockMessage(),
+ )
+
+ for mock in mocks:
+ with self.subTest(mock=mock):
+ with self.assertRaises(AttributeError):
+ mock.the_cake_is_a_lie
+
+ def test_custom_mock_methods_are_valid_discord_object_methods(self):
+ """The `AsyncMock` attributes of the mocks should be valid for the class they're mocking."""
+ mocks = (
+ (helpers.MockGuild, helpers.guild_instance),
+ (helpers.MockRole, helpers.role_instance),
+ (helpers.MockMember, helpers.member_instance),
+ (helpers.MockBot, helpers.bot_instance),
+ (helpers.MockContext, helpers.context_instance),
+ (helpers.MockTextChannel, helpers.channel_instance),
+ (helpers.MockMessage, helpers.message_instance),
+ )
+
+ for mock_class, instance in mocks:
+ mock = mock_class()
+ async_methods = (
+ attr for attr in dir(mock) if isinstance(getattr(mock, attr), helpers.AsyncMock)
+ )
+
+ # spec_mock = unittest.mock.MagicMock(spec=instance)
+ for method in async_methods:
+ with self.subTest(mock_class=mock_class, method=method):
+ try:
+ getattr(instance, method)
+ except AttributeError:
+ msg = f"method {method} is not a method attribute of {instance.__class__}"
+ self.fail(msg)
+
+ @unittest.mock.patch(f'{__name__}.DiscordMocksTests.subTest')
+ def test_the_custom_mock_methods_test(self, subtest_mock):
+ """The custom method test should raise AssertionError for invalid methods."""
+ class FakeMockBot(helpers.AttributeMock, unittest.mock.MagicMock):
+ """Fake MockBot class with invalid attribute/method `release_the_walrus`."""
+
+ attribute_mocktype = unittest.mock.MagicMock
+
+ def __init__(self, **kwargs):
+ super().__init__(spec=helpers.bot_instance, **kwargs)
+
+ # Fake attribute
+ self.release_the_walrus = helpers.AsyncMock()
+
+ with unittest.mock.patch("tests.helpers.MockBot", new=FakeMockBot):
+ msg = "method release_the_walrus is not a valid method of <class 'discord.ext.commands.bot.Bot'>"
+ with self.assertRaises(AssertionError, msg=msg):
+ self.test_custom_mock_methods_are_valid_discord_object_methods()
+
+
+class MockObjectTests(unittest.TestCase):
+ """Tests the mock objects and mixins we've defined."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.hashable_mocks = (helpers.MockRole, helpers.MockMember, helpers.MockGuild)
+
+ def test_colour_mixin(self):
+ """Test if the ColourMixin adds aliasing of color -> colour for child classes."""
+ class MockHemlock(unittest.mock.MagicMock, helpers.ColourMixin):
+ pass
+
+ hemlock = MockHemlock()
+ hemlock.color = 1
+ self.assertEqual(hemlock.colour, 1)
+ self.assertEqual(hemlock.colour, hemlock.color)
+
+ def test_hashable_mixin_hash_returns_id(self):
+ """Test if the HashableMixing uses the id attribute for hashing."""
+ class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
+ pass
+
+ scragly = MockScragly()
+ scragly.id = 10
+ self.assertEqual(hash(scragly), scragly.id)
+
+ def test_hashable_mixin_uses_id_for_equality_comparison(self):
+ """Test if the HashableMixing uses the id attribute for hashing."""
+ class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
+ pass
+
+ scragly = MockScragly(spec=object)
+ scragly.id = 10
+ eevee = MockScragly(spec=object)
+ eevee.id = 10
+ python = MockScragly(spec=object)
+ python.id = 20
+
+ self.assertTrue(scragly == eevee)
+ self.assertFalse(scragly == python)
+
+ def test_hashable_mixin_uses_id_for_nonequality_comparison(self):
+ """Test if the HashableMixing uses the id attribute for hashing."""
+ class MockScragly(unittest.mock.Mock, helpers.HashableMixin):
+ pass
+
+ scragly = MockScragly(spec=object)
+ scragly.id = 10
+ eevee = MockScragly(spec=object)
+ eevee.id = 10
+ python = MockScragly(spec=object)
+ python.id = 20
+
+ self.assertTrue(scragly != python)
+ self.assertFalse(scragly != eevee)
+
+ def test_mock_class_with_hashable_mixin_uses_id_for_hashing(self):
+ """Test if the MagicMock subclasses that implement the HashableMixin use id for hash."""
+ for mock in self.hashable_mocks:
+ with self.subTest(mock_class=mock):
+ instance = helpers.MockRole(role_id=100)
+ self.assertEqual(hash(instance), instance.id)
+
+ def test_mock_class_with_hashable_mixin_uses_id_for_equality(self):
+ """Test if MagicMocks that implement the HashableMixin use id for equality comparisons."""
+ for mock_class in self.hashable_mocks:
+ with self.subTest(mock_class=mock_class):
+ instance_one = mock_class()
+ instance_two = mock_class()
+ instance_three = mock_class()
+
+ instance_one.id = 10
+ instance_two.id = 10
+ instance_three.id = 20
+
+ self.assertTrue(instance_one == instance_two)
+ self.assertFalse(instance_one == instance_three)
+
+ def test_mock_class_with_hashable_mixin_uses_id_for_nonequality(self):
+ """Test if MagicMocks that implement HashableMixin use id for nonequality comparisons."""
+ for mock_class in self.hashable_mocks:
+ with self.subTest(mock_class=mock_class):
+ instance_one = mock_class()
+ instance_two = mock_class()
+ instance_three = mock_class()
+
+ instance_one.id = 10
+ instance_two.id = 10
+ instance_three.id = 20
+
+ self.assertFalse(instance_one != instance_two)
+ self.assertTrue(instance_one != instance_three)
+
+ def test_spec_propagation_of_mock_subclasses(self):
+ """Test if the `spec` does not propagate to attributes of the mock object."""
+ test_values = (
+ (helpers.MockGuild, "region"),
+ (helpers.MockRole, "mentionable"),
+ (helpers.MockMember, "display_name"),
+ (helpers.MockBot, "owner_id"),
+ (helpers.MockContext, "command_failed"),
+ )
+
+ for mock_type, valid_attribute in test_values:
+ with self.subTest(mock_type=mock_type, attribute=valid_attribute):
+ mock = mock_type()
+ self.assertTrue(isinstance(mock, mock_type))
+ attribute = getattr(mock, valid_attribute)
+ self.assertTrue(isinstance(attribute, mock_type.attribute_mocktype))
+
def test_async_mock_provides_coroutine_for_dunder_call(self):
"""Test if AsyncMock objects have a coroutine for their __call__ method."""
async_mock = helpers.AsyncMock()