What's the best way to write unit tests for code using the Python 3.4 asyncio
library? Assume I want to test a TCP client (SocketConnection
):
import asyncio
import unittest
class TestSocketConnection(unittest.TestCase):
def setUp(self):
self.mock_server = MockServer("localhost", 1337)
self.socket_connection = SocketConnection("localhost", 1337)
@asyncio.coroutine
def test_sends_handshake_after_connect(self):
yield from self.socket_connection.connect()
self.assertTrue(self.mock_server.received_handshake())
When running this test case with the default test runner, the test will always succeed as the method executes only up until the first yield from
instruction, after which it returns before executing any assertions. This causes tests to always succeed.
Is there a prebuilt test runner that is able to handle asynchronous code like this?
You can also use
aiounittest
that takes similar approach as @Andrew Svetlov, @Marvin Killing answers and wrap it in easy to useAsyncTestCase
class:As you can see the async case is handled by
AsyncTestCase
. It supports also synchronous test. There is a possibility to provide custom event loop, just overrideAsyncTestCase.get_event_loop
.If you prefer (for some reason) the other TestCase class (eg
unittest.TestCase
), you might useasync_test
decorator:I usually define my async tests as coroutines and use a decorator for "syncing" them:
Use this class instead of
unittest.TestCase
base class:Really like the
async_test
wrapper mentioned in https://stackoverflow.com/a/23036785/350195, here is an updated version for Python 3.5+async_test
, suggested by Marvin Killing, definitely can help -- as well as direct callingloop.run_until_complete()
But I also strongly recommend to recreate new event loop for every test and directly pass loop to API calls (at least
asyncio
itself acceptsloop
keyword-only parameter for every call that need it).Like
that isolates tests in test case and prevents strange errors like longstanding coroutine that has been created in
test_a
but finished only ontest_b
execution time.I temporarily solved the problem using a decorator inspired by Tornado's gen_test:
Like J.F. Sebastian suggested, this decorator will block until the test method coroutine has finished. This allows me to write test cases like this:
This solution probably misses some edge cases.
I think a facility like this should added to Python's standard library to make
asyncio
andunittest
interaction more convenient out of the box.