aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2020-02-29 17:06:51 +0100
committerGravatar Sebastiaan Zeeff <[email protected]>2020-02-29 17:06:51 +0100
commitc2af442676011eb620593505789be4d34da76ea3 (patch)
tree05a89c295bdfc74ad7438d4e7a3a6ff2bff55fb1 /tests
parentMerge branch 'master' into python38-migration (diff)
Migrate snekbox tests to Python 3.8's unittest
I've migrated the `tests/test_snekbox.py` file to use the new Python 3.8-style unittests instead of our old style using our custom Async mocks. In particular, I had to make a few changes: - Mocking the async post() context manager correctly Since `ClientSession.post` returns an async context manager when called, we need to make sure to assign the return value to the __aenter__ method of whatever `post()` returns, not of `post` itself (i.e.. when it's not called). - Use the new AsyncMock assert methods `assert_awaited_once` and `assert_awaited_once_with` Objects of the new `unittest.mock.AsyncMock` class have special methods to assert what they were called with that also assert that specific coroutine object was awaited. This means we test two things in one: Whether or not it was called with the right arguments and whether or not the returned coroutine object was then awaited. - Patch `functools.partial` as `partial` objects are compared by identity When you create two partial functions of the same function, you'll end up with two different `partial` objects. Since `partial` objects are compared by identity, you can't compare a `partial` created in a test method to that created in the callable you're trying to test. They will always compare as `False`. Since we're not interested in actually creating `partial` objects, I've just patched `functools.partial` in the namespace of the module we're testing to make sure we can compare them.
Diffstat (limited to 'tests')
-rw-r--r--tests/bot/cogs/test_snekbox.py68
1 files changed, 27 insertions, 41 deletions
diff --git a/tests/bot/cogs/test_snekbox.py b/tests/bot/cogs/test_snekbox.py
index 985bc66a1..9cd7f0154 100644
--- a/tests/bot/cogs/test_snekbox.py
+++ b/tests/bot/cogs/test_snekbox.py
@@ -1,74 +1,68 @@
import asyncio
import logging
import unittest
-from functools import partial
-from unittest.mock import MagicMock, Mock, call, patch
+from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
from bot.cogs import snekbox
from bot.cogs.snekbox import Snekbox
from bot.constants import URLs
-from tests.helpers import (
- AsyncContextManagerMock, AsyncMock, MockBot, MockContext, MockMessage, MockReaction, MockUser, async_test
-)
+from tests.helpers import MockBot, MockContext, MockMessage, MockReaction, MockUser
-class SnekboxTests(unittest.TestCase):
+class SnekboxTests(unittest.IsolatedAsyncioTestCase):
def setUp(self):
"""Add mocked bot and cog to the instance."""
self.bot = MockBot()
-
- self.mocked_post = MagicMock()
- self.mocked_post.json = AsyncMock()
- self.bot.http_session.post = MagicMock(return_value=AsyncContextManagerMock(self.mocked_post))
-
self.cog = Snekbox(bot=self.bot)
- @async_test
async def test_post_eval(self):
"""Post the eval code to the URLs.snekbox_eval_api endpoint."""
- self.mocked_post.json.return_value = {'lemon': 'AI'}
+ resp = MagicMock()
+ resp.json = AsyncMock(return_value="return")
+ self.bot.http_session.post().__aenter__.return_value = resp
- self.assertEqual(await self.cog.post_eval("import random"), {'lemon': 'AI'})
- self.bot.http_session.post.assert_called_once_with(
+ self.assertEqual(await self.cog.post_eval("import random"), "return")
+ self.bot.http_session.post.assert_called_with(
URLs.snekbox_eval_api,
json={"input": "import random"},
raise_for_status=True
)
+ resp.json.assert_awaited_once()
- @async_test
async def test_upload_output_reject_too_long(self):
"""Reject output longer than MAX_PASTE_LEN."""
result = await self.cog.upload_output("-" * (snekbox.MAX_PASTE_LEN + 1))
self.assertEqual(result, "too long to upload")
- @async_test
async def test_upload_output(self):
"""Upload the eval output to the URLs.paste_service.format(key="documents") endpoint."""
- key = "RainbowDash"
- self.mocked_post.json.return_value = {"key": key}
+ key = "MarkDiamond"
+ resp = MagicMock()
+ resp.json = AsyncMock(return_value={"key": key})
+ self.bot.http_session.post().__aenter__.return_value = resp
self.assertEqual(
await self.cog.upload_output("My awesome output"),
URLs.paste_service.format(key=key)
)
- self.bot.http_session.post.assert_called_once_with(
+ self.bot.http_session.post.assert_called_with(
URLs.paste_service.format(key="documents"),
data="My awesome output",
raise_for_status=True
)
- @async_test
async def test_upload_output_gracefully_fallback_if_exception_during_request(self):
"""Output upload gracefully fallback if the upload fail."""
- self.mocked_post.json.side_effect = Exception
+ resp = MagicMock()
+ resp.json = AsyncMock(side_effect=Exception)
+ self.bot.http_session.post().__aenter__.return_value = resp
+
log = logging.getLogger("bot.cogs.snekbox")
with self.assertLogs(logger=log, level='ERROR'):
await self.cog.upload_output('My awesome output!')
- @async_test
async def test_upload_output_gracefully_fallback_if_no_key_in_response(self):
"""Output upload gracefully fallback if there is no key entry in the response body."""
- self.mocked_post.json.return_value = {}
self.assertEqual((await self.cog.upload_output('My awesome output!')), None)
def test_prepare_input(self):
@@ -121,7 +115,6 @@ class SnekboxTests(unittest.TestCase):
actual = self.cog.get_status_emoji({'stdout': stdout, 'returncode': returncode})
self.assertEqual(actual, expected)
- @async_test
async def test_format_output(self):
"""Test output formatting."""
self.cog.upload_output = AsyncMock(return_value='https://testificate.com/')
@@ -172,7 +165,6 @@ class SnekboxTests(unittest.TestCase):
with self.subTest(msg=testname, case=case, expected=expected):
self.assertEqual(await self.cog.format_output(case), expected)
- @async_test
async def test_eval_command_evaluate_once(self):
"""Test the eval command procedure."""
ctx = MockContext()
@@ -186,7 +178,6 @@ class SnekboxTests(unittest.TestCase):
self.cog.send_eval.assert_called_once_with(ctx, 'MyAwesomeFormattedCode')
self.cog.continue_eval.assert_called_once_with(ctx, response)
- @async_test
async def test_eval_command_evaluate_twice(self):
"""Test the eval and re-eval command procedure."""
ctx = MockContext()
@@ -201,7 +192,6 @@ class SnekboxTests(unittest.TestCase):
self.cog.send_eval.assert_called_with(ctx, 'MyAwesomeFormattedCode')
self.cog.continue_eval.assert_called_with(ctx, response)
- @async_test
async def test_eval_command_reject_two_eval_at_the_same_time(self):
"""Test if the eval command rejects an eval if the author already have a running eval."""
ctx = MockContext()
@@ -214,7 +204,6 @@ class SnekboxTests(unittest.TestCase):
"@LemonLemonishBeard#0042 You've already got a job running - please wait for it to finish!"
)
- @async_test
async def test_eval_command_call_help(self):
"""Test if the eval command call the help command if no code is provided."""
ctx = MockContext()
@@ -222,14 +211,13 @@ class SnekboxTests(unittest.TestCase):
await self.cog.eval_command.callback(self.cog, ctx=ctx, code='')
ctx.invoke.assert_called_once_with(self.bot.get_command("help"), "eval")
- @async_test
async def test_send_eval(self):
"""Test the send_eval function."""
ctx = MockContext()
ctx.message = MockMessage()
ctx.send = AsyncMock()
ctx.author.mention = '@LemonLemonishBeard#0042'
- ctx.typing = MagicMock(return_value=AsyncContextManagerMock(None))
+
self.cog.post_eval = AsyncMock(return_value={'stdout': '', 'returncode': 0})
self.cog.get_results_message = MagicMock(return_value=('Return code 0', ''))
self.cog.get_status_emoji = MagicMock(return_value=':yay!:')
@@ -244,14 +232,13 @@ class SnekboxTests(unittest.TestCase):
self.cog.get_results_message.assert_called_once_with({'stdout': '', 'returncode': 0})
self.cog.format_output.assert_called_once_with('')
- @async_test
async def test_send_eval_with_paste_link(self):
"""Test the send_eval function with a too long output that generate a paste link."""
ctx = MockContext()
ctx.message = MockMessage()
ctx.send = AsyncMock()
ctx.author.mention = '@LemonLemonishBeard#0042'
- ctx.typing = MagicMock(return_value=AsyncContextManagerMock(None))
+
self.cog.post_eval = AsyncMock(return_value={'stdout': 'Way too long beard', 'returncode': 0})
self.cog.get_results_message = MagicMock(return_value=('Return code 0', ''))
self.cog.get_status_emoji = MagicMock(return_value=':yay!:')
@@ -267,14 +254,12 @@ class SnekboxTests(unittest.TestCase):
self.cog.get_results_message.assert_called_once_with({'stdout': 'Way too long beard', 'returncode': 0})
self.cog.format_output.assert_called_once_with('Way too long beard')
- @async_test
async def test_send_eval_with_non_zero_eval(self):
"""Test the send_eval function with a code returning a non-zero code."""
ctx = MockContext()
ctx.message = MockMessage()
ctx.send = AsyncMock()
ctx.author.mention = '@LemonLemonishBeard#0042'
- ctx.typing = MagicMock(return_value=AsyncContextManagerMock(None))
self.cog.post_eval = AsyncMock(return_value={'stdout': 'ERROR', 'returncode': 127})
self.cog.get_results_message = MagicMock(return_value=('Return code 127', 'Beard got stuck in the eval'))
self.cog.get_status_emoji = MagicMock(return_value=':nope!:')
@@ -289,8 +274,8 @@ class SnekboxTests(unittest.TestCase):
self.cog.get_results_message.assert_called_once_with({'stdout': 'ERROR', 'returncode': 127})
self.cog.format_output.assert_not_called()
- @async_test
- async def test_continue_eval_does_continue(self):
+ @patch("bot.cogs.snekbox.partial")
+ async def test_continue_eval_does_continue(self, partial_mock):
"""Test that the continue_eval function does continue if required conditions are met."""
ctx = MockContext(message=MockMessage(add_reaction=AsyncMock(), clear_reactions=AsyncMock()))
response = MockMessage(delete=AsyncMock())
@@ -299,15 +284,16 @@ class SnekboxTests(unittest.TestCase):
actual = await self.cog.continue_eval(ctx, response)
self.assertEqual(actual, 'NewCode')
- self.bot.wait_for.has_calls(
- call('message_edit', partial(snekbox.predicate_eval_message_edit, ctx), timeout=10),
- call('reaction_add', partial(snekbox.predicate_eval_emoji_reaction, ctx), timeout=10)
+ self.bot.wait_for.assert_has_awaits(
+ (
+ call('message_edit', check=partial_mock(snekbox.predicate_eval_message_edit, ctx), timeout=10),
+ call('reaction_add', check=partial_mock(snekbox.predicate_eval_emoji_reaction, ctx), timeout=10)
+ )
)
ctx.message.add_reaction.assert_called_once_with(snekbox.REEVAL_EMOJI)
ctx.message.clear_reactions.assert_called_once()
response.delete.assert_called_once()
- @async_test
async def test_continue_eval_does_not_continue(self):
ctx = MockContext(message=MockMessage(clear_reactions=AsyncMock()))
self.bot.wait_for.side_effect = asyncio.TimeoutError