Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 75 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,30 +562,96 @@ see also [tests/test_defer.py](https://github.com/randy3k/UnitTesting-example/bl

### Asyncio testing

Tests for `asyncio` are written using `IsolatedAsyncioTestCase` class.
Tests for `asyncio` use `AsyncTestCase` or `AsyncViewTestCase` class.

It auto-detects type of `setUp()`, `tearDown()`, and `test_..()` methods.
Those can be synchronous methods or async coroutine functions.

Asynchronous coroutine functions are executed in default event loop,
provided by [sublime_aio][].


```py
import asyncio
import sublime

from unittesting import AsyncTestCase


async def async_coroutine(view):

def run_in_mainthread():
view.run_command("select_all")
view.run_command("right_delete")
view.run_command("insert", {"characters": "Modified Content"})

sublime.set_timeout(run_in_mainthread, 10)
await asyncio.sleep(2.0)


class MyAsyncTestCase(AsyncTestCase):

@classmethod
async def setUpClass(cls):
pass

@classmethod
async def tearDownClass(cls):
pass

from unittesting import IsolatedAsyncioTestCase
async def setUp(self):
self.view = sublime.active_window().new_file()
self.view.set_scratch(True)
self.view.run_command("insert", {"characters": "Initial Content"})

async def a_coro():
return 1 + 1
async def tearDown(self):
self.view.close()

class MyAsyncTestCase(IsolatedAsyncioTestCase):
async def test_something(self):
result = await a_coro()
await asyncio.sleep(1)
self.assertEqual(result, 2)
async def test_setup_completed(self):
self.assertEqual(
self.view.substr(sublime.Region(0, self.view.size())),
"Initial Content"
)

async def test_coroutine(self):
await async_coroutine(self.view)
self.assertEqual(
self.view.substr(sublime.Region(0, self.view.size())),
"Modified Content"
)
```

To run coroutines in a custom event loop, override static `run_override()` method.

```py
import sublime_aio
from unittesting import AsyncTestCase


class MyAsyncTestCase(AsyncTestCase):

def run_coroutine(coro: abc.meta.Coroutine) -> cuncurrent.futures.Future:
return sublime_aio.run_coroutine(coro)
```

Note, asyncio event loops must not block Sublime Text's main thread.

> [!WARNING]
>
> Do not use `unittest.IsolatedAsyncioTestCase` class,
> as it spins up a blocking event loop in main thread,
> which prevents any synchronous command from being executed
> by Sublime Text.

[sublime_aio]: https://github.com/packagecontrol/sublime_aio


## Helper TestCases

UnitTesting provides some helper test case classes,
which perform common tasks such as overriding preferences, setting up views, etc.

- AsyncViewTestCase
- DeferrableViewTestCase
- OverridePreferencesTestCase
- TempDirectoryTestCase
Expand Down
6 changes: 5 additions & 1 deletion dependencies.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"*": {
">3000": [
"3000 - 3999": [
"coverage"
],
">4000": [
"coverage",
"sublime_aio"
]
}
}
47 changes: 38 additions & 9 deletions tests/_Asyncio/tests/test_coroutine.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import asyncio

from unittest import skipIf
from unittesting import IsolatedAsyncioTestCase
from unittesting import AsyncViewTestCase


async def a_coro():
return 1 + 1
async def a_coro(test):
await asyncio.sleep(1.0)
test.setText("Modified Content")


class MyAsyncTestCase(IsolatedAsyncioTestCase):
async def test_something(self):
result = await a_coro()
await asyncio.sleep(1)
self.assertEqual(result, 2)
class MyAsyncTestCaseA(AsyncViewTestCase):

test_class_initiated = 0

@classmethod
async def setUpClass(cls):
assert cls.test_class_initiated == 0
cls.test_class_initiated = 1

@classmethod
async def tearDownClass(cls):
cls.test_class_initiated = 2

async def setUp(self):
self.setText("Initial Content")

async def tearDown(self):
self.setText("")

async def test_class_setup_completed(self):
self.assertEqual(self.test_class_initiated, 1)

async def test_setup_completed(self):
self.assertViewContentsEqual("Initial Content")

async def test_coroutine(self):
await a_coro(self)
self.assertViewContentsEqual("Modified Content")


class MyAsyncTestCaseB(AsyncViewTestCase):

async def test_class_setup_completed(self):
self.assertEqual(MyAsyncTestCaseA.test_class_initiated, 2)
2 changes: 1 addition & 1 deletion tests/_Asyncio/unittesting.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"deferred": false
"deferred": true
}
10 changes: 10 additions & 0 deletions tests/_Deferred/tests/test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
from unittesting import TestCase
from unittesting import DeferrableViewTestCase
from unittesting import expectedFailure


class TestDefaultTestCase(TestCase):

def test_simple_assert(self):
self.assertTrue(True)


class TestDeferrable(DeferrableViewTestCase):

def test_simple_assert(self):
self.assertTrue(True)

def test_defer(self):
self.setText("foo")
self.setCaretTo(0, 0)
Expand Down
6 changes: 4 additions & 2 deletions unittesting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from .core import AWAIT_WORKER
from .core import AsyncTestCase
from .core import DeferrableMethod
from .core import DeferrableTestCase
from .core import IsolatedAsyncioTestCase
from .core import TestCase
from .core import expectedFailure
from .helpers import AsyncViewTestCase
from .helpers import DeferrableViewTestCase
from .helpers import OverridePreferencesTestCase
from .helpers import TempDirectoryTestCase
Expand All @@ -12,12 +13,13 @@


__all__ = [
"AsyncTestCase",
"AsyncViewTestCase",
"AWAIT_WORKER",
"DeferrableMethod",
"DeferrableTestCase",
"DeferrableViewTestCase",
"expectedFailure",
"IsolatedAsyncioTestCase",
"OverridePreferencesTestCase",
"run_scheduler",
"TempDirectoryTestCase",
Expand Down
8 changes: 4 additions & 4 deletions unittesting/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import sys

if sys.version_info >= (3, 13):
from .py313.case import AsyncTestCase
from .py313.case import DeferrableMethod
from .py313.case import DeferrableTestCase
from .py313.case import IsolatedAsyncioTestCase
from .py313.case import TestCase
from .py313.case import expectedFailure
from .py313.loader import DeferrableTestLoader
from .py313.runner import AWAIT_WORKER
from .py313.runner import DeferringTextTestRunner
from .py313.suite import DeferrableTestSuite
elif sys.version_info >= (3, 8):
from .py38.case import AsyncTestCase
from .py38.case import DeferrableMethod
from .py38.case import DeferrableTestCase
from .py38.case import IsolatedAsyncioTestCase
from .py38.case import TestCase
from .py38.case import expectedFailure
from .py38.loader import DeferrableTestLoader
from .py38.runner import AWAIT_WORKER
from .py38.runner import DeferringTextTestRunner
from .py38.suite import DeferrableTestSuite
elif sys.version_info >= (3, 3):
from .py33.case import AsyncTestCase
from .py33.case import DeferrableMethod
from .py33.case import DeferrableTestCase
from .py33.case import IsolatedAsyncioTestCase
from .py33.case import TestCase
from .py33.case import expectedFailure
from .py33.loader import DeferrableTestLoader
Expand All @@ -34,13 +34,13 @@
raise ImportError("Unsupported python runtime!")

__all__ = [
"AsyncTestCase",
"AWAIT_WORKER",
"DeferrableMethod",
"DeferrableTestCase",
"DeferrableTestLoader",
"DeferrableTestSuite",
"DeferringTextTestRunner",
"IsolatedAsyncioTestCase",
"TestCase",
"expectedFailure",
]
Loading
Loading