HTTP integration testing

The things specified in this section are only applicable if you are using Tavern to test a HTTP API (ie, unless you are specifically checking MQTT or some other plugin).

Using multiple status codes

If the server you are contacting might return one of a few different status codes depending on it’s internal state, you can write a test that has a list of status codes in the expected response.

Say for example we want to try and get a user’s details from a server - if it exists, it returns a 200. If not, it returns a 404. We don’t care which one, as long as it it only one of those two codes.

---

test_name: Make sure that the server will either return a 200 or a 404

stages:
  - name: Try to get user
    request:
      url: "{host}/users/joebloggs"
      method: GET
    response:
      status_code:
        - 200
        - 404

Note that there is no way to do something like this for the body of the response, so unless you are expecting the same response body for every possible status code, the json key should be left blank.

Sending form encoded data

Though Tavern can only currently verify JSON data in the response, data can be sent using x-www-form-urlencoded encoding by using the data key instead of json in a request. An example of sending form data rather than json:

    request:
      url: "{test_host}/form_data"
      method: POST
      data:
        id: abc123

Authorisation

Persistent cookies

Tavern uses requests under the hood, and uses a persistent Session for each test. This means that cookies are propagated forward to further stages of a test. Cookies can also be required to pass a test. For example, say we have a server that returns a cookie which then needs to be used for future requests:

---

test_name: Make sure cookie is required to log in

includes:
  - !include common.yaml

stages:
  - name: Try to check user info without login information
    request:
      url: "{host}/userinfo"
      method: GET
    response:
      status_code: 401
      json:
        error: "no login information"
      headers:
        content-type: application/json

  - name: login
    request:
      url: "{host}/login"
      json:
        user: test-user
        password: correct-password
      method: POST
      headers:
        content-type: application/json
    response:
      status_code: 200
      cookies:
        - session-cookie
      headers:
        content-type: application/json

  - name: Check user info
    request:
      url: "{host}/userinfo"
      method: GET
    response:
      status_code: 200
      json:
        name: test-user
      headers:
        content-type: application/json

This test ensures that a cookie called session-cookie is returned from the ‘login’ stage, and this cookie will be sent with all future stages of that test.

Choosing cookies

If you have multiple cookies for a domain, the cookies key can also be used in the request block to specify which one to send:

---

test_name: Test receiving and sending cookie

includes:
  - !include common.yaml

stages:
  - name: Expect multiple cookies returned
    request:
      url: "{host}/get_cookie"
      method: POST
    response:
      status_code: 200
      cookies:
        - tavern-cookie-1
        - tavern-cookie-2

  - name: Only send one cookie
    request:
      url: "{host}/expect_cookie"
      method: GET
      cookies:
        - tavern-cookie-1
    response:
      status_code: 200
      json:
        status: ok

Trying to specify a cookie which does not exist will fail the stage.

To send no cookies, simply use an empty array:

---

test_name: Test receiving and sending cookie

includes:
  - !include common.yaml

stages:
  - name: get cookie for domain
    request:
      url: "{host}/get_cookie"
      method: POST
    response:
      status_code: 200
      cookies:
        - tavern-cookie-1

  - name: Send no cookies
    request:
      url: "{host}/expect_cookie"
      method: GET
      cookies: []
    response:
      status_code: 403
      json:
        status: access denied

Overriding cookies

If you want to override the value of a cookie, then instead of passing a string to the cookies block in the request, use a mapping of cookie name: cookie value:

  - name: Override cookie value
    request:
      url: "{host}/expect_cookie"
      method: GET
      cookies:
        - tavern-cookie-2: abc
    response:
      status_code: 200
      json:
        status: ok

This will create a new cookie with the name tavern-cookie-2 with the value abc and send it in the request. If this cookie already exists from a previous stage, it will be overwritten. Trying to override the cookie multiple times in one stage will cause an error to occur at runtime.

HTTP Basic Auth

For a server that expects HTTP Basic Auth, the auth keyword can be used in the request block. This expects a list of two items - the first item is the user name, and the second name is the password:

---

test_name: Check we can access API with HTTP basic auth

includes:
  - !include common.yaml

stages:
  - name: Get user info
    request:
      url: "{host}/userinfo"
      method: GET
      auth:
        - user@api.com
        - password123
    response:
      status_code: 200
      json:
        user_id: 123
      headers:
        content-type: application/json

Custom auth header

If you’re using a form of authorisation not covered by the above two examples to authorise against your test server (for example, a JWT-based system), specify a custom Authorization header. If you are using a JWT, you can use the built in validate_jwt external function as defined above to check that the claims are what you’d expect.

---

test_name: Check we can login then use a JWT to access the API

includes:
  - !include common.yaml

stages:
  - name: login
    request:
      url: "{host}/login"
      json:
        user: test-user
        password: correct-password
      method: POST
      headers:
        content-type: application/json
    response:
      status_code: 200
      json:
        $ext: &verify_token
          function: tavern.testutils.helpers:validate_jwt
          extra_kwargs:
            jwt_key: "token"
            key: CGQgaG7GYvTcpaQZqosLy4
            options:
              verify_signature: true
              verify_aud: true
              verify_exp: true
            audience: testserver
      headers:
        content-type: application/json
      save:
        json:
          test_login_token: token

  - name: Get user info
    request:
      url: "{host}/userinfo"
      method: GET
      Authorization: "Bearer {test_login_token:s}"
    response:
      status_code: 200
      json:
        user_id: 123
      headers:
        content-type: application/json

Controlling secure access

Running against an unverified server

If you’re testing against a server which has SSL certificates that fail validation (for example, testing against a local development server with self-signed certificates), the verify keyword can be used in the request stage to disable certificate checking for that request.

Using self signed certificates

In case you need to use a self-signed certificate to connect to a server, you can use the cert key in the request to control which certificates will be used by Requests.

If you just want to pass your client certificate with a request, pass the path to it using the cert key:

---

test_name: Access an API which requires a client certificate

stages:
  - name: Get user info
    request:
      url: "{host}/userinfo"
      method: GET
      cert: "/path/to/certificate"
      # Or use a format variable:
      # cert: "{cert_path}"
    response:
      ...

If you need to pass a SSL key file as well, pass a list of length two with the first element being the certificate and the second being the path to the key:

---

test_name: Access an API which requires a client certificate

stages:
  - name: Get user info
    request:
      url: "{host}/userinfo"
      method: GET
      cert:
        - "/path/to/certificate"
        - "/path/to/key"
    response:
      ...

See the Requests documentation for more details about this option.

Uploading files as part of the request

To upload a file along with the request, the files key can be used:

---

test_name: Test files can be uploaded with tavern

includes:
  - !include common.yaml

stages:
  - name: Upload multiple files
    request:
      url: "{host}/fake_upload_file"
      method: POST
      files:
        test_files: "test_files.tavern.yaml"
        common: "common.yaml"
    response:
      status_code: 200

This expects a mapping of the ‘name’ of the file in the request to the path on your computer.

By default, the sending of files is handled by the Requests library - to see the implementation details, see their documentation.

Uploading a file as the body of a request

In some cases it may be required to upload the entire contents of a file in the request body - for example, when posting a binary data blob from a file. This can be done for JSON and YAML using the !include tag, but for other data formats the file_body key can be used:

  - name: Upload a file in the request body
    request:
      url: "{host}/data_blob"
      method: POST
      file_body: "/path/to/blobfile
    response:
      status_code: 200

Like the files key, this is mutually exclusive with the json key.

Specifying custom content type and encoding

If you need to use a custom file type and/or encoding when uploading the file, there is a ‘long form’ specification for uploading files. Instead of just passing the path to the file to upload, use the file_path and content_type/content_encoding in the block for the file:

---

test_name: Test files can be uploaded with tavern

stages:
  - name: Upload multiple files
    request:
      url: "{host}/fake_upload_file"
      method: POST
      files:
        # simple style - guess the content type and encoding
        test_files: "test_files.tavern.yaml"
        # long style - specify them manually
        common:
          file_path: "common.yaml"
          content_type: "application/customtype"
          content_encoding: "UTF16"

Timeout on requests

If you want to specify a timeout for a request, this can be done using the timeout parameter:

---
test_name: Get server info from slow endpoint

stages:
  - name: Get info
    request:
      url: "{host}/get-info-slow"
      method: GET
      timeout: 0.5
    response:
      status_code: 200
      json:
        n_users: 2048
        n_queries: 10000

If this request takes longer than 0.5 seconds to respond, the test will be considered as failed. A 2-tuple can also be passed - the first value will be a connection timeout, and the second value will be the response timeout. By default this uses the Requests implementation of timeouts - see their documentation for more details.

Redirects

By default, Tavern will not follow redirects. This allows you to check whether an endpoint is indeed redirecting a user to a certain page.

To disable this behaviour, use either the --tavern-always-follow-redirects command line flag or set tavern-always-follow-redirects to True in your Pytest settings file.

This can also be disabled or enabled on a per-stage basis by using the follow_redirects flag:

---
test_name: Expect a redirect when setting the flag

stages:
  - name: Expect to be redirected
    request:
      url: "{host}/redirect/source"
      follow_redirects: true
    response:
      status_code: 200
      json:
        status: successful redirect

Specifying follow_redirects on a stage will override any global setting, so if you just want to change the behaviour for one stage then use this flag.