项目作者: nytimes

项目描述 :
A simple concurrent HTTP testing tool
高级语言: Go
项目地址: git://github.com/nytimes/httptest.git
创建时间: 2019-03-20T14:52:15Z
项目社区:https://github.com/nytimes/httptest

开源协议:Apache License 2.0

下载


httptest

BuildRelease

A simple concurrent HTTP testing tool

Usage

Write a simple test

Create a file tests.yml with the following content:

  1. tests:
  2. - description: 'root' # Description, will be printed with test results
  3. request: # Request to send
  4. path: '/' # Path
  5. response: # Expected response
  6. statusCodes: [200] # List of expected response status codes

Run tests locally

This program is distributed as a Docker image. To run a container locally (under a folder contains tests related yml file):

  1. docker run --rm \
  2. -v $(pwd)/httpbin.yml:/tests/tests.yml \
  3. -e "TEST_HOST=example.com" \
  4. nytimes/httptest

You should see an output similar to this:

  1. passed: tests.yml | root | /
  2. 1 passed
  3. 0 failed
  4. 0 skipped

Tip: If your test cases have conditions on environment variables (see conditions in full example), remember to include -e "<ENV_VAR>=<value>". e.g.

  1. docker run --rm \
  2. -v $(pwd)/route-abc-tests.yml:/tests/tests.yml \
  3. -e "TEST_HOST=www.stg.example.com" \
  4. -e "TEST_ENV=stg" \
  5. nytimes/httptest

By default, if your current working directory is $(pwd)/tests, the program will parse all files in $(pwd)/tests recursively.
This can be changed using an environment variable.

Run tests in a CI/CD pipeline

This image can be used in any CI/CD system that supports Docker containers.

Examples

  • Drone

    1. pipeline:
    2. tests:
    3. image: nytimes/httptest
    4. pull: true
    5. environment:
    6. TEST_HOST: 'example.com'
  • GitHub Actions

    1. action "httptest" {
    2. uses = "docker://nytimes/httptest"
    3. env = {
    4. TEST_HOST = "example.com"
    5. }
    6. }

Configurations

A few global configurations (applied to all tests) can be specified by
environment variables:

  • TEST_DIRECTORY: Local directory that contains the test definition YML or YAML
    files. Default: tests

  • TEST_HOST: Host to test. Can be overridden by request.host of individual
    test definitions. If TEST_HOST and request.host are both not set, test
    will fail.

  • TEST_CONCURRENCY: Maximum number of concurrent requests at a time.
    Default: 2.

  • TEST_DNS_OVERRIDE: Override the IP address for TEST_HOST. Does not work
    for request.host specified in YML.

  • TEST_PRINT_FAILED_ONLY: Only print failed tests. Valid values: false or
    true. Default: false.

  • TEST_VERBOSITY: Increase logging output for tests. Default: 0.

  • ENABLE_RETRIES: Enables retrying requests if a test does not succeed.
    Defaults: false.

  • DEFAULT_RETRY_COUNT: Specify the number of times to retry a test request if
    the initial request does not succeed. Only applied if ENABLE_RETRIES is set
    to true Defaults: 2.

Environment variable substitution

This program supports variable substitution from environment variables in YML
files. This is useful for handling secrets or dynamic values.
Visit here for
supported functions.

Example:

  1. tests:
  2. - description: 'get current user'
  3. request:
  4. path: '/user'
  5. headers:
  6. authorization: 'token ${SECRET_AUTH_TOKEN}'
  7. response:
  8. statusCodes: [200]

Dynamic headers

In addition to defining header values statically (i.e. when a test is written), there is also support for determining the value of a header at runtime. This feature is useful if the value of a header must be determined when a test runs (for example, a header that must contain a recent timestamp). Such headers are specified as dynamicHeaders in the YAML, and each must be accompanied by the name of a function to call and a list of arguments for that function (if required). For example:

  1. dynamicHeaders:
  2. - name: x-test-signature
  3. function: myFunction
  4. args:
  5. - 'arg1'
  6. - 'arg2'
  7. - 'arg3'

The arguments can be literal strings, environment variables, or values of previously set headers (see examples below). The function definitions are in the file dynamic.go and can be added to as necessary. Each function must implement the following function interface:

  1. type resolveHeader func(existingHeaders map[string]string, args []string) (string, error)

These are the functions that are currently supported:

now

Returns the number of seconds since the Unix epoch

Args: none

signStringRS256PKCS8

Constructs a string from args (delimited by newlines), signs it with the (possibly passphrase-encrypted) PKCS #8 private key, and returns the signature in base64

Args:

  • key
  • passphrase (can be empty string if key is not encrypted)
  • string(s)… (any amount of strings, from previously set headers or literal values)

postFormURLEncoded

Sends an HTTP POST request to the given URL (with Content-Type application/x-www-form-urlencoded), returns either the whole response body or a specific JSON element, and treats the remaining arguments as data

Args:

  • url (the URL to send the request to)
  • element (see section on “JSON Query Syntax” below)
  • string(s)… (any amount of strings, from previously set headers or literal values, which will be concatenated and delimited with ‘&’ for the request body)
JSON Query Syntax

The element to return from the JSON response body is specified by a dot-delimited string representing the path to the element. Each part of this string is a single step in that path, and array access is handled by providing a zero-based index. If the element selected has a primitive value, that value is returned. If the element selected is a JSON object or array, that object/array is returned in compact form (insignificant whitespace removed). To return the entire JSON response body, use an empty string.

Examples:
Primitive data
Response body:

  1. {
  2. "nested": {
  3. "object": {
  4. "data": 123
  5. }
  6. }
  7. }

Query: ‘nested.object.data’
Result: 123

Object
Response body:

  1. {
  2. "nested": {
  3. "object": {
  4. "data": 123
  5. }
  6. }
  7. }

Query: ‘nested.object’
Result: {“data”:123}

Array
Response body:

  1. {
  2. "nested": {
  3. "array": [
  4. "abc",
  5. "def",
  6. "ghi"
  7. ]
  8. }
  9. }

Query: ‘nested.array’
Result: [“abc”,”def”,”ghi”]

Array element
Response body:

  1. {
  2. "nested": {
  3. "array": [
  4. "abc",
  5. "def",
  6. "ghi"
  7. ]
  8. }
  9. }

Query: ‘nested.array.1’
Result: def

Whole response
Response body:

  1. Just a normal, non-JSON response

Query: ‘’
Result: Just a normal, non-JSON response

concat

Concatenates the arguments into a single string

Args:

  • string(s)… (any amount of strings, from previously set headers or literal values, which will be concatenated)

Full test example

Required fields for each test:

  • description
  • request.path

All other fields are optional. All matchings are case insensitive.

  1. tests:
  2. - description: 'root' # Description, will be printed with test results. Required
  3. conditions: # Specify conditions. Test only runs when all conditions are met
  4. env: # Matches an environment variable
  5. TEST_ENV: '^(dev|stg)$' # Environment variable name : regular expression
  6. skipCertVerification: false # Set true to skip verification of server TLS certificate (insecure and not recommended)
  7. request: # Request to send
  8. scheme: 'https' # URL scheme. Only http and https are supported. Default: https
  9. host: 'example.com' # Host to test against (this overrides TEST_HOST for this specific test)
  10. method: 'POST' # HTTP method. Default: GET
  11. path: '/' # Path to hit. Required
  12. headers: # Headers
  13. x-test-header-0: 'abc'
  14. x-test: '${REQ_TEST}' # Environment variable substitution
  15. dynamicHeaders: # Headers whose values are determined at runtime (see "Dynamic Headers" section above)
  16. - name: x-test-timestamp # Name of the header to set
  17. function: now # Calling a no-arg function to get the header value
  18. - name: x-test-signature
  19. function: signStringRS256PKCS8 # Function called to determine the header value
  20. args: # List of arguments for the function (can be omitted for functions that don't take arguments)
  21. - '${TEST_KEY}' # Can use environment variable substitution
  22. - '${TEST_PASS}'
  23. - x-test-timestamp # Can use values of previously set headers
  24. - '/svc/login' # Can use literals
  25. - x-test-header-0
  26. - x-test
  27. - name: x-test-token
  28. function: postFormURLEncoded # Function called to retrieve the header value from an API
  29. args:
  30. - '${TOKEN_URL}' # The URL to POST to
  31. - 'results.7.token.id_token' # The dot-delimited string representing an element to return from the JSON response body; use integers for array elements or '' for the whole response body
  32. - 'client_secret=${CLIENT_SECRET}' # Can use literals, environment variables, or a combination
  33. - x-test-timestamp # Can use values of previously set headers
  34. - 'client_id=123' # These arguments will be combined to send 'client_secret=${CLIENT_SECRET}&x-test-timestamp&client_id=123' in the body of the request
  35. - name: authorization
  36. function: concat # Function that concatenates strings, either literals or values from previously set headers
  37. args:
  38. - 'Bearer '
  39. - x-test-token
  40. body: '' # Request body. Processed as string
  41. response: # Expected response
  42. statusCodes: [201] # List of expected response status codes
  43. headers: # Expected response headers
  44. patterns: # Match response header patterns
  45. server: '^ECS$' # Header name : regular expression
  46. cache-control: '.+'
  47. notPresent: # Specify headers not expected to exist.
  48. - 'set-cookie' # These are not regular expressions
  49. - 'x-frame-options'
  50. notMatching:
  51. set-cookie: ^.*abc.*$ # Specify headers expected to exist but NOT match the given regex
  52. body: # Response body
  53. patterns: # Response body has to match all patterns in this list in order to pass test
  54. - 'charset="utf-8"' # Regular expressions
  55. - 'Example Domain'
  56. - description: 'sign up page' # Second test
  57. request:
  58. path: '/signup'
  59. response:
  60. statusCodes: [200]
  61. - description: 'HTTPS GET - test if present not matching'
  62. request:
  63. path: '/get'
  64. conditions:
  65. env:
  66. TEST_ENV: dev
  67. response:
  68. statusCodes: [200]
  69. headers:
  70. ifPresentNotMatching:
  71. Content-Type: ^notreal/fake$ # If Content-Type does not exist or does not match this pattern the test passes, else it fails
  72. - description: 'HTTPS GET - test if present not matching multiple possible matches'
  73. request:
  74. path: '/get'
  75. conditions:
  76. env:
  77. TEST_ENV: dev
  78. response:
  79. headers:
  80. notMatching:
  81. Server: ^(.*(nginx|42)).* # If Server does not match either portion of the pattern (nginx or 42) the test passes, else it fails
  82. statusCodes:
  83. - 200

Development

Run locally

Download package

  1. go get -d -u github.com/nytimes/httptest

Build and run

  1. # In repo root directory
  2. make run

This will run the tests defined in example-tests directory.