Testing

Testing (which includes unit testing, integration testing, and regression testing) is very important for quality code; extremely so if the code is a library that will be used in other software.

Test Framework: pytest

aiosmtpd uses the pytest testing framework. Advanced features of pytest are widely used throughout.

Plugins

The one required plugin is pytest-mock; it is used extensively throughout the test suite.

Other plugins that are used, to various degrees, in the aiosmtpd test suite are:

  • pytest-cov to integrate with coverage-py

  • pytest-sugar to provide better ux

  • pytest-print to give some progress indicator and to assist test troubleshooting

  • pytest-profiling to implement *-profile testenv, although to be honest this is not really useful as the profiling gets ‘muddied’ by pytest runner.

Fixtures

Below is a list of fixtures defined throught the test suite, in alphabetical order:

fixture aiosmtpd.tests.conftest.client

Scope:    function

Generic SMTP Client, will connect to the host:port defined in Global.SrvAddr unless overriden using client_data() marker.

fixture aiosmtpd.tests.conftest.get_controller

Scope:    function

Provides a function that will return an instance of a controller.

Default class of the controller is Controller, but can be changed via the class_ parameter to the function, or via the class_ parameter of controller_data()

Example usage:

def test_case(get_controller):
    handler = SomeHandler()
    controller = get_controller(handler, class_=SomeController)
    ...
Parameters:

class_ – The class of the controller to be instantiated. If given, overrides class_ arg of controller_data(). If not specified and no class_ from controller_data, defaults to ExposingController.

Returns:

an instance of Controller (or a subclass of)

In addition to explicitly-specified parameters, get_controller also fetches all *args and **kwargs parameters from controller_data() marker.

fixture aiosmtpd.tests.conftest.get_handler

Scope:    function

Provides a function that will return an instance of a handler class.

Default class of the handler is Sink, but can be changed via the class_ parameter to the function, or via the class_ parameter of handler_data()

Example usage:

def test_case(get_handler):
    handler = get_handler(class_=SomeHandler)
    controller = Controller(handler)
    ...
Parameters:

class_ – The class of the handler to be instantiated. If given, overrides class_ arg of handler_data(). If not specified and no class_ from handler_data, defaults to Sink.

Returns:

an instance of the handler class.

In addition to explicitly-specified parameters, get_handler also fetches all *args and **kwargs parameters from handler_data() marker.

fixture aiosmtpd.tests.conftest.nodecode_controller

Scope:    function

Same as plain_controller, except that decode_data=False is enforced.

This is actually identical to using plain_controller with marker @controller_data(decode_data=False). But because this is used in a lot of test cases, it’s tidier to just make this into a dedicated fixture.

fixture aiosmtpd.tests.conftest.plain_controller

Scope:    function

Returns a Controller that, by default, gets invoked with no optional args. Hence the moniker “plain”.

Internally uses the get_controller and get_handler fixtures, so optional args/kwargs can be specified for the Controller and the handler via the controller_data() and handler_data() markers, respectively.

fixture aiosmtpd.tests.conftest.silence_event_loop_closed

Scope:    module

Mostly used to suppress “unhandled exception” error due to _ProactorBasePipeTransport raising an exception when doing __del__

fixture aiosmtpd.tests.conftest.ssl_context_client

Scope:    function

Provides a client-side SSL Context

fixture aiosmtpd.tests.conftest.ssl_context_server

Scope:    function

Provides a server-side SSL Context

Important

As long as you create your test module(s) inside the aiosmtpd/tests directory, you do not need to import the above fixtures; they will automatically be available for use as they are defined in the conftest.py file.

Note

Individual test modules may define their own module-specific fixtures; please refer to their respective docstrings for description / usage guide.

Markers

@client_data(...)

Provides parameters to the client fixture.

Parameters:

connect_to (HostPort) – Address to connect to. Defaults to Global.SrvAddr

@controller_data(...)

Provides parameters to the get_controller fixture.

Parameters:
  • class_ – The class to be instantiated by get_controller. Will be overridden if get_controller is invoked with the class_ argument.

  • host_port (str) – The “host:port” to bound to

  • **kwargs – Keyworded arguments given to the marker.

@handler_data(...)

Provides parameters to the get_handler fixture.

Parameters:
  • args_ – A tuple containing values that will be passed as positional arguments to the controller constructor

  • class_ – The class to be instantiated by get_controller

  • *args – Positional arguments given to the marker. Will override the args_ keyword argument

  • **kwargs – Keyworded arguments given to the marker.