Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Plugins

Tavern has a simple plugin system which lets you change how requests are made. By default, backends are handled by:

However, there are some situations where you might not want to run tests against something other than a live server, or maybe you just want to use curl to extract some better usage statistics out of your requests. Tavern’s plugin system can be used to override this default behaviour.

The best way to introduce the concepts for making a plugin is by using an example. For this we will be looking at a plugin used to run tests against a local flask server called tavern_flask.

There is another plugin used to run tests against FastAPI/Starlette TestClient called tavern_fastapi which may also be of interest.

Community plugins

The following plugins are maintained outside the core Tavern repository:

The entry point

Plugins are loaded using two setuptools entry points, namely tavern_http for HTTP tests and tavern_mqtt for MQTT tests. The built-in requests and paho-mqtt functionality is implemented using plugins, so looking at the _plugins folder in the Tavern repository will also be useful as a reference when writing a plugin.

The entry point needs to point to either a class or a module which defines a preset number of variables.

Something like this should be in your setup.py, setup.cfg, poetry.toml, pyproject.toml, etc. to make sure Tavern can pick it up at run time:

# setup.cfg

# A http plugin. tavern_http is the entry point that Tavern searches for,
# 'requests' is the name of your plugin which is selected using the
# --tavern-http-backend command line flag. This points to a class in the
# tavernhook module.
tavern_http =
    requests = tavern._plugins.rest.tavernhook:TavernRestPlugin

# An MQTT plugin. Like above, tavern_mqtt is the entry point name and
# 'paho-mqtt' is the name of the plugin. This points to a module.
tavern_mqtt =
    paho-mqtt = tavern._plugins.mqtt.tavernhook

Examples:

Extra schema data

If your plugin needs extra metadata in each test to be able to make a request, extra schema data can be added with a schema key in your entry point. This should be a dictionary which is merged into the base schema for tests.

Examples:

Session type

session_type should return a class which describes a “session” which will be used throughout the entire test. It should be a class that fulfils two requirements:

  1. It must take the same keyword arguments as the ‘base’ session object to create an instance for testing. For HTTP tests this is the same arguments as a requests.Session object, and for MQTT tests it is the same arguments as specified in the MQTT documentation. If your plugin does not support some of these arguments, raise a NotImplementedError which a short message explaining that it is not supported.

  2. After creating the instance, it must be able to be used as a context manager. If you don’t need any functionality provided by this, you can define empty __enter__ and __exit__ methods on your class like so:

class MySession:

    def __enter__(self):
        pass

    def __exit__(self, *args):
        pass

Examples:

Request

request_type is a class that encapsulates the concept of a ‘request’ for your plugin. It takes 3 arguments:

In the constructor, this request type should validate the input data and format the request variables given the test block config.

The class should also have a run method, which takes no arguments and is called to run the test. This should return some kind of class encapsulating response data which can be verified by your plugin’s response verifier class.

Tavern knows which request keyword (eg request, mqtt_publish) corresponds to your plugin by matching it to the plugin’s request_block_name. For the moment, this should be hardcoded to request for HTTP tests.

Examples:

Getting the expected response

get_expected_from_request should be a function that takes 3 arguments:

This function should use this input data to calculate the expected response and perform any extra things that need doing based on the request or expected response. This will normally just be formatting the response block based on the variables in the test block config, but you may need to do extra things (such as subscribing to an MQTT topic).

Examples:

Response

verifier_type is a class that encapsulate the concept of verifying a response for your plugin. It should inherit from tavern.response.base.BaseResponse, and take 4 arguments:

It should also define a couple of methods:

Like with a request, Tavern knows which verifier to use by looking at the response_block_name key.

Examples:

Advanced - Multiple Responses

If your plugin supports multiple responses (e.g., subscribing to multiple MQTT topics or GraphQL subscriptions), you can:

  1. Set has_multiple_responses = True in your plugin.

  2. In get_expected_from_request, return a list of expected responses instead of a single one, under a new block name that is distinct from the response_block_name. An example from the MQTT plugin:

    expected = {"mqtt_responses": []}
    if isinstance(response_block, dict):
        response_block = [response_block]
    
    for response in response_block:
        # format so we can subscribe to the right topic
        f_expected = format_keys(response, test_block_config.variables)
        mqtt_client = session
        mqtt_client.subscribe(f_expected["topic"], f_expected.get("qos", 1))
        expected["mqtt_responses"].append(f_expected)
    
    return expected
  3. When calling super().__init__(...) in your response_type, pass multiple_responses_block="<name_of_block>" where <name_of_block> is the name of the block you used in step 2.

This tells Tavern to expect a list of responses instead of a single response block.

When enabled, Tavern will:

If your plugin does not support multiple responses, set has_multiple_responses = False (or omit it - it defaults to False) and don’t pass multiple_responses_block to super().__init__.