Twisted support

testtools provides support for testing Twisted code.

Matching Deferreds

testtools provides support for making assertions about synchronous Deferreds.

A “synchronous” Deferred is one that does not need the reactor or any other asynchronous process in order to fire.

Normal application code can’t know when a Deferred is going to fire, because that is generally left up to the reactor. Well-written unit tests provide fake reactors, or don’t use the reactor at all, so that Deferreds fire synchronously.

These matchers allow you to make assertions about when and how Deferreds fire, and about what values they fire with.

See also Testing Deferreds without the reactor and the Deferred howto.

Running tests in the reactor

testtools provides support for running asynchronous Twisted tests: tests that return a Deferred and run the reactor until it fires and its callback chain is completed.

Here’s how to use it:

from testtools import TestCase
from testtools.twistedsupport import AsynchronousDeferredRunTest

class MyTwistedTests(TestCase):

    run_tests_with = AsynchronousDeferredRunTest

    def test_foo(self):
        # ...
        return d

Note that you do not have to use a special base TestCase in order to run Twisted tests, you should just use the regular testtools.TestCase base class.

You can also run individual tests within a test case class using the Twisted test runner:

class MyTestsSomeOfWhichAreTwisted(TestCase):

    def test_normal(self):
        pass

    @run_test_with(AsynchronousDeferredRunTest)
    def test_twisted(self):
        # ...
        return d

See AsynchronousDeferredRunTest and AsynchronousDeferredRunTestForBrokenTwisted for more information.

Controlling the Twisted logs

Users of Twisted Trial will be accustomed to all tests logging to _trial_temp/test.log. By default, AsynchronousDeferredRunTest will not do this, but will instead:

  1. suppress all messages logged during the test run
  2. attach them as the twisted-log detail (see Details) which is shown if the test fails

The first behavior is controlled by the suppress_twisted_logging parameter to AsynchronousDeferredRunTest, which is set to True by default. The second is controlled by the store_twisted_logs parameter, which is also True by default.

If store_twisted_logs is set to False, you can still get the logs attached as a detail by using the CaptureTwistedLogs fixture. Using the CaptureTwistedLogs fixture is equivalent to setting store_twisted_logs to True.

For example:

class DoNotCaptureLogsTests(TestCase):
    run_tests_with = partial(AsynchronousDeferredRunTest,
                             store_twisted_logs=False)

    def test_foo(self):
        log.msg('logs from this test are not attached')

    def test_bar(self):
        self.useFixture(CaptureTwistedLogs())
        log.msg('logs from this test *are* attached')

Converting Trial tests to testtools tests

  • Use the AsynchronousDeferredRunTest runner
  • Make sure to upcall to TestCase.setUp() and TestCase.tearDown()
  • Don’t use setUpClass or tearDownClass
  • Don’t expect setting .todo, .timeout or .skip attributes to do anything
  • Replace twisted.trial.unittest.SynchronousTestCase.flushLoggedErrors() with flush_logged_errors()
  • Replace twisted.trial.unittest.TestCase.assertFailure() with assert_fails_with()
  • Trial spins the reactor a couple of times before cleaning it up, AsynchronousDeferredRunTest does not. If you rely on this behavior, use AsynchronousDeferredRunTestForBrokenTwisted.