Package: aiofastforward
aiofastforwardFast-forward time in asyncio Python by patchingloop.call_later,loop.call_at,loop.time, andasyncio.sleep. This allows you to test asynchronous code synchronously.Inspired byAngularJS $timeout.$flush.InstallationpipinstallaiofastforwardUsagePatching is done through a context manager, similar tounittest.patch.importasynciofromaiofastforwardimportFastForwardloop=asyncio.get_event_loop()withFastForward(loop)asforward:# Call production function(s), that call asyncio.sleep, loop.call_later,# loop.call_at, or loop.time# ...# Fast-forward time 1 second# asyncio.sleeps, and loop.call_at and loop.call_later callbacks# will be called: as though 1 second of real-world time has passedawaitforward(1)# More production functions or assertions# ...Examplesasyncio.sleep# Production codeasyncdefsleeper(callback):awaitasyncio.sleep(1)callback(0)awaitasyncio.sleep(2)# Test codefromunittest.mockimportMock,callloop=asyncio.get_event_loop()callback=Mock()withaiofastforward.FastForward(loop)asforward:asyncio.ensure_future(sleeper())awaitforward(1)# Move time forward one secondself.assertEqual(callback.mock_calls,[])awaitforward(1)# Move time forward another secondself.assertEqual(callback.mock_calls,[call(0)])loop.call_later# Production codeasyncdefschedule_callback(loop,callback):loop.call_later(1,callback,0)loop.call_later(2,callback,1)# Test codefromunittest.mockimportMock,callloop=asyncio.get_event_loop()withaiofastforward.FastForward(loop)asforward:callback=Mock()awaitschedule_callback(loop,callback)awaitforward(1)# Move time forward one secondself.assertEqual(callback.mock_calls,[call(0)])awaitforward(1)# Move time forward another secondself.assertEqual(callback.mock_calls,[call(0),call(1)])loop.call_at# Production codeasyncdefschedule_callback(loop,callback):now=loop.time()loop.call_at(now+1,callback,0)loop.call_at(now+2,callback,1)# Test codefromunittest.mockimportMock,callloop=asyncio.get_event_loop()withaiofastforward.FastForward(loop)asforward:callback=Mock()awaitschedule_callback(loop,callback)awaitforward(1)# Move time forward one secondself.assertEqual(callback.mock_calls,[call(0)])awaitforward(1)# Move time forward another secondself.assertEqual(callback.mock_calls,[call(0),call(1)])forwarding time can blockawait forward(a)only moves time forward, i.e. resolve calls toasyncio.sleepor calls the callbacks ofcall_atorcall_later, once there are sufficient such calls that time could have progressed that amount. Calls to IO functions, even if they take non-zero amounts of real time in the test, do not advance the patched "pseudo-timeline": they are treated as instantanous.This means that there are cases whereawait forward(a)will block forever.# Production codeasyncdefsleeper():awaitasyncio.sleep(1)# Test codeloop=asyncio.get_event_loop()withaiofastforward.FastForward(loop)asforward:asyncio.ensure_future(sleeper())awaitforward(2)# Will block foreverTo avoid this, ensure you onlyawait forwardan amount less than or equal to how much pseudo-time that will be progressed byasyncio.sleep,call_atorcall_later.# Production codeasyncdefsleeper(callback):awaitasyncio.sleep(1)callback(0)awaitasyncio.sleep(1)callback(1)# Test codefromunittest.mockimportMock,callloop=asyncio.get_event_loop()withaiofastforward.FastForward(loop)asforward:asyncio.ensure_future(sleeper(callback))start_time=loop.time()awaitforward(1.5)# The second sleep will have been called, but not resolvedself.assertEqual(loop.time(),start_time+1.5)self.assertEqual(callback.mock_calls,[call(0)])The justification for this design are the consequences of the the alternative: if itwouldn'tblock. This would mean that all sleeps and callbacks would have to be registeredbeforethe call toforward, and this in turn would lead to less flexible test code.For example, the production code may have a chain of 10asyncio.sleep(1), and in the test you would like toawait forward(10)to assert on the state of the system after these. At the time of callingawait forward(10)however, at most one of theasyncio.sleep(1)would have been called. Not blocking would mean that afterawait forward(10), the pseudo-timeline in the world of the patched production code would not have moved forward ten seconds.Differences between aiofastforward.FastForward andasynctest.ClockedTestCaseThere is overlap in functionality: both support fast-forwarding time in terms of loop.call_later and loop.call_at. However, there are properties that FastForward has that ClockedTestCase does not:FastForward is not coupled to any particular test framework. The only requirement is that the test code must be in an async function. If you wish, you can use FastForward in anasynctest.TestCasetest.FastForward supports fast-forwarding asyncio.sleep.FastForward allows fast-forwarding time in any event loop, not just the one the test code runs in.ClockedTestCase does have an advantage over FastForward, which may be important for some uses:ClockedTestCase supports Python 3.4 onwards, while FastForward supports Python 3.5.0 onwards.