Writing Tests¶
Ra uses a simple DSL for describing tests in a structured way, similar to the RAML definition.
Resource scopes:¶
Tests are organized by resource in “resource scopes”, which can be nested:
api = ra.api('example.raml')
@api.resource('/users')
def users_resource(users):
# tests for /users resource
@users.resource('/{username}')
def user_resource(user):
# tests for /users/{username} resource
The resource scope (e.g. function users_resource
above) takes an
argument: it will be passed a ResourceScope
object that is used
within the scope to define tests or nested scopes.
By default, requests made in resource tests will use the example body
defined in the RAML if it exists (only 'application/json'
is currently
supported). You can override this behaviour and use custom resource
factories:
def user_factory():
import string
import random
name = ''.join(random.choice(string.ascii_lowercase) for _ in range(10))
email = "{}@example.com".format(name)
return dict(username=name, email=email, password=name)
@api.resource('/users')
def users_resource(users, factory=user_factory):
# tests ...
Also by default, resources with URI parameters will have the parameter filled with the example value defined in the RAML if it exists. It can be overrided when the scope is declared:
# ...
/users/{username}:
uriParameters:
username:
type: string
example: finn
# ...
@users.resource('/users/{username}')
def user_resource(user):
# {username} will be "finn"
# ...
# or:
@users.resource('/users/{username}', username='jake')
def user_resource_overriding_username(user):
# {username} will be "jake"
# ...
Either way, for testing an item resource, you’ll probably want to use fixtures (see Test fixtures) to set up a resource by that name before these tests.
pytest fixtures defined in resource scopes are local to that scope (behind the scenes, resource scopes are treated just like modules by pytest):
@users.resource('/users')
def users_resource(users):
# local to this scope:
@pytest.fixture
def myfixture():
return 1
# ...
Tests¶
Within resource scopes, define tests for the methods available on that resource.
@users.resource('/users')
def users_resource(users):
@user.get
def get(req):
# do some test-specific setup ...
response = req()
# do some WebTest assertions on the response ...
The test function parameter req
is provided by a pytest fixture.
It’s a callable webob.request.RequestBase
-like request object that
is pre-bound to the app that was passed (or assumed) when we called
ra.api()
, as well as the resource scope’s path and the test’s method
declaration. (Note on req
naming: request
is already a builtin
fixture name in pytest.)
To override request parameters, you can pass them into the test decorator:
@user.get(content_type='text/plain')
def get_text(req):
req()
Or pass request parameters directly into req()
,. You can also pass which
status codes are considered a success (default is 2xx/3xx status codes, this
is standard WebTest):
@users.get
def get_text(req):
req(content_type='text/plain', status=(200, 404))
You can also override the resource scope’s factory declaration (or the default RAML example factories) on individual tests. The factory generates request data which is encoded as a JSON body:
@api.resource('/users', factory=users_factory)
def users_resource(users):
@users.post(factory=users_post_factory)
def post_with_my_factory(req):
assert req.factory == users_post_factory
# factory is used to generate data (an object)
assert req.data == users_post_factory()
# data is encoded to body as a JSON bytestring
import json
assert req.body == bytes(json.dumps(req.data, cls=req.JSONEncoder))
req()
By default, responses are validated against the RAML definition, checking the body and headers are compliant. You can disable this:
@user.get
def get_with_no_validation(req):
req(validate=False)
# or only validate body (valid values are "body", "headers")
req(validate=['body'])
Because tests are collected by pytest, you can pass any other fixtures you want to the test function:
@pytest.fixture
def two_hundred():
return 200
@user.get
def get_with_fixture(req, two_hundred):
response = req()
assert response.status_code == two_hundred