Test Fixtures

You can use pytest’s autouse fixtures to define any test setup or teardown. Here’s an example of cleaning the database before and after testing:

@pytest.fixture(autouse=True)
def clear_database(request, req, app):
    # Remember:
    # - ``req`` is the pre-bound Ra request for the current test
    # - ``request`` is a built-in pytest fixture that holds info about
    #   the current test context
    # - ``app`` is the webtest-wrapped application
    import example_app
    example_app.clear_db()
    example_app.start_transaction()

    # login for authentication
    app.post('/login', { 'login': 'system', 'password': '123456' })

    if req.match(only='/users', exclude='POST'):
        # for tests on the /users resource, unless it's a POST, we should
        # create the user first
        example_app.create_user_in_db("marcy")

    @request.addfinalizer
    def fin():
        example_app.rollback_transaction()
        app.reset() # clear cookies; logout

def user_factory():
    user = {
        'username': 'marcy',
        #  ...
    }
    return user

@api.resource('/users')
def users_resource(users):
    @users.get
    def get(req):
        response = req()
        assert 'marcy' in response

    @users.post(factory=user_factory)
    def post(req):
        response = req()
        assert 'marcy' in response

In this example, we use a pytest fixture to clear the database in between each test, as well as start a database transaction. A finalizer is added, which runs after the test, to rollback the transaction. It also logs in as the default system user (for authentication purposes) and, conditionally, creates a user.

req.match() is used to test the current request’s method and path against patterns (see below about only and exclude). It matches for tests on the /users resource, for all HTTP methods except POST. This means the user exists for the GET request, but not for the POST request where it will be created by the request (to avoid conflict).

This ensures that tests are isolated from one another, but still have access to the objects they expect (based on the examples in the RAML, usually).

Note that req.match() also applies to tests generated by autotest (see Autotest <./autotest.html>_), making it a good way to customize behaviour around these tests.

Resource-specific setup

Resource scopes have their own fixture scope, so you can provide test setup that applies to tests within a resource easily:

@api.resource('/users')
def users_resource(users):

    @pytest.fixture(autouse=True)
    def users_setup():
        # ...

    @users.get
    def get(): req()

    # ...

only and exclude

req.match() can be called with the parameters only and exclude. These parameters each take a string pattern or an array of string patterns describing routes by HTTP method, path, or both. Path patterns can match routes with glob syntax.

If only is omitted, all tests will match unless they are excluded using exclude. Patterns in exclude always take precedence.

Some examples:

@pytest.fixture(autouse=True)
def setup(req):
    if req.match(only=['GET /users', 'POST /users']):
        # matches only tests for "GET /users" and "POST /users" exactly
        pass

    if req.match(exclude='POST')
        # matches all tests unless the method is POST
        pass

    if req.match(only='/users', exclude=['POST', 'PUT'])
        # matches tests for path "/users" unless method is POST or PUT
        pass

    if req.match('/users/*')
        # ``only`` is the first positional argument, so you can call it
        # this way; matches any path starting with "/users/"
        # (doesn't match "/users")
        pass

    if req.match('*/edit')
        # matches any path ending with "/edit", e.g. "/posts/{id}/edit"
        pass