Skip to content

Commit

Permalink
Implemented async_unittest for writing tests for asynchronous code
Browse files Browse the repository at this point in the history
Added logging call if main loop is exited unexpectedly
  • Loading branch information
MosesofEgypt committed Jun 16, 2017
1 parent f468d47 commit 2ff4fdc
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/main.py
Expand Up @@ -4,6 +4,8 @@
from ui import MainWindow
from common import get_app, get_loop, set_app_icon

# call get_app and get_loop to have app and loop
# be created in the globals of the common module
app = get_app()
loop = get_loop()
set_app_icon()
Expand All @@ -13,6 +15,7 @@
main_window.show()
try:
loop.run_until_complete(main_window.main_loop())
logging.warning("main loop exited unexpectedly")
loop.run_forever()
except RuntimeError as e:
logging.exception(e)
Expand Down
42 changes: 42 additions & 0 deletions test/async_unittest.py
@@ -0,0 +1,42 @@
import asyncio
from unittest import case, loader, mock, result, runner, signals, suite, util
from unittest import * # import everything that IS defined in __all__


def AsyncMock(mock_func, *args, **kwargs):
'''
Returns an asynchronous function coroutine. The provided "mock_func"
argument is the function to be called when the coroutine is run.
All args and kwargs are passed to the mock when it is called.
'''
async def mock_coroutine(*args, **kwargs):
return mock_func(*args, **kwargs)

mock_coroutine.mock = mock_func
return mock_coroutine


def AsyncMagicMock(*args, **kwargs):
'''
Returns an asynchronous function coroutine. The returned coroutine
contains a "mock" attribute, which is the MagicMock object to be called
when the coroutine is run. All args and kwargs are passed to the mock.
'''
return AsyncMock(mock.MagicMock(*args, **kwargs), *args, **kwargs)


class AsyncTestCase(TestCase):
_loop = None

@property
def loop(self):
if self._loop is None:
self._loop = asyncio.get_event_loop()
return self._loop

@loop.setter
def loop(self, new_val):
self._loop = new_val

def run_async(self, coroutine):
return self.loop.run_until_complete(coroutine)
4 changes: 2 additions & 2 deletions test/test_download.py
@@ -1,5 +1,5 @@
import requests
from unittest import TestCase, main, mock
from async_unittest import AsyncTestCase, TestCase, mock, main
from download import download_file, Download, DownloadTracker


Expand Down Expand Up @@ -155,7 +155,7 @@ def _call_download(self, test_path, test_url,
test_download_file_no_session_provided


class DownloadTrackerTest(TestCase):
class DownloadTrackerTest(AsyncTestCase):
download_tracker = None
progress_bar = ProgressBarMock()
executor = ExecutorMock()
Expand Down
4 changes: 2 additions & 2 deletions test/test_ui.py
Expand Up @@ -6,8 +6,8 @@
import shutil
import subprocess
import sys
from async_unittest import AsyncTestCase, TestCase, mock, main
from PyQt5 import QtWidgets
from unittest import TestCase, mock, main
from util import namedtuple_from_mapping, get_platform

config.setup_directories()
Expand Down Expand Up @@ -263,7 +263,7 @@ def subprocess_Popen_mock(args):
self.assertFalse(mock_data['sys_exit_called'])


class UiTest(TestCase):
class UiTest(AsyncTestCase):

def setUp(self):
common.get_app()
Expand Down

0 comments on commit 2ff4fdc

Please sign in to comment.