Integration testing REST APIs is a crucial part of ensuring the reliability of micro-services and web applications. While there are many tools available, using simple .http files offers a lightweight and version-controllable approach that I really love.

In this post, I’ll explore how to use HTTP File Runner (or httprunner), a command-line tool I built in Rust, to execute advanced integration test scenarios using .http files. We’ll cover everything from variable management to conditional execution and CI/CD integration.

Getting Started

First, ensure you have httprunner installed. You can install it via a simple script or download a release from the GitHub repository.

# Linux/macOS
curl -fsSL https://christianhelle.com/httprunner/install | bash

# Windows
irm https://christianhelle.com/httprunner/install.ps1 | iex

If you’re on Ubuntu then you can also install it using snap

snap install httprunner

Once installed, you can run any .http file:

httprunner tests.http

Global Variables

You can define global variables at the top of your .http file using the @ syntax. This is perfect for values that are reused across multiple requests, like a base URL.

@HostAddress = https://httpbin.org
@ContentType = application/json

GET {{HostAddress}}/get
Content-Type: {{ContentType}}

Built-in Functions

httprunner provides built-in functions for dynamic value generation in your .http files. Functions are case-insensitive and automatically generate values when the request is executed.

Available Functions

guid() - Generate UUID

Generates a new UUID v4 (Universally Unique Identifier) in simple format (32 hex characters without dashes).

POST https://api.example.com/users
Content-Type: application/json

{
  "id": "guid()",
  "requestId": "GUID()"
}

string() - Generate Random String

Generates a random alphanumeric string of 20 characters.

POST https://api.example.com/test
Content-Type: application/json

{
  "sessionKey": "string()",
  "token": "STRING()"
}

number() - Generate Random Number

Generates a random number between 0 and 100 (inclusive).

POST https://api.example.com/data
Content-Type: application/json

{
  "randomValue": "number()",
  "percentage": "NUMBER()"
}

base64_encode() - Base64 Encoding

Encodes a string to Base64 format. The string must be enclosed in single quotes.

POST https://api.example.com/auth
Content-Type: application/json

{
  "credentials": "base64_encode('username:password')",
  "token": "BASE64_ENCODE('Hello, World!')"
}

upper() - Convert to Uppercase

Converts a string to uppercase. The string must be enclosed in single quotes.

POST https://api.example.com/data
Content-Type: application/json

{
  "code": "upper('hello, world')",
  "shout": "UPPER('quiet text')"
}

lower() - Convert to Lowercase

Converts a string to lowercase. The string must be enclosed in single quotes.

POST https://api.example.com/data
Content-Type: application/json

{
  "normalized": "lower('HELLO, WORLD')",
  "lowercase": "LOWER('Mixed Case Text')"
}

name() - Generate Full Name

Generates a random full name (first name + last name).

POST https://api.example.com/users
Content-Type: application/json

{
  "fullName": "name()",
  "displayName": "NAME()"
}

first_name() - Generate First Name

Generates a random first name.

POST https://api.example.com/users
Content-Type: application/json

{
  "firstName": "first_name()",
  "givenName": "FIRST_NAME()"
}

last_name() - Generate Last Name

Generates a random last name.

POST https://api.example.com/users
Content-Type: application/json

{
  "lastName": "last_name()",
  "surname": "LAST_NAME()"
}

address() - Generate Address

Generates a random full mailing address (street, city, postal code, country).

POST https://api.example.com/users
Content-Type: application/json

{
  "streetAddress": "address()",
  "mailingAddress": "ADDRESS()"
}

email() - Generate Email Address

Generates a random email address in the format firstname.lastname@domain.com.

POST https://api.example.com/users
Content-Type: application/json

{
  "email": "email()",
  "contactEmail": "EMAIL()"
}

job_title() - Generate Job Title

Generates a random job title.

POST https://api.example.com/users
Content-Type: application/json

{
  "title": "job_title()",
  "position": "JOB_TITLE()"
}

lorem_ipsum(n) - Generate Lorem Ipsum Text

Generates Lorem Ipsum placeholder text with the specified number of words. The parameter n determines how many words to generate.

POST https://api.example.com/content
Content-Type: application/json

{
  "summary": "lorem_ipsum(20)",
  "description": "LOREM_IPSUM(50)",
  "content": "Lorem_Ipsum(100)"
}

getdate() - Get Current Date

Returns the current local date in YYYY-MM-DD format.

POST https://api.example.com/events
Content-Type: application/json

{
  "eventDate": "getdate()",
  "createdDate": "GETDATE()"
}

gettime() - Get Current Time

Returns the current local time in HH:MM:SS format (24-hour).

POST https://api.example.com/logs
Content-Type: application/json

{
  "timestamp": "gettime()",
  "logTime": "GETTIME()"
}

getdatetime() - Get Current Date and Time

Returns the current local date and time in YYYY-MM-DD HH:MM:SS format.

POST https://api.example.com/records
Content-Type: application/json

{
  "createdAt": "getdatetime()",
  "timestamp": "GETDATETIME()"
}

getutcdatetime() - Get Current UTC Date and Time

Returns the current UTC date and time in YYYY-MM-DD HH:MM:SS format.

POST https://api.example.com/records
Content-Type: application/json

{
  "utcTimestamp": "getutcdatetime()",
  "serverTime": "GETUTCDATETIME()"
}

Environment Variables

For different environments (development, staging, production), you shouldn’t hard code values. httprunner supports loading variables from a http-client.env.json file, compatible with the VS Code REST Client extension. In most cases, the environment file would contain secrets like API keys or tokens that you don’t want to commit to version control.

Create a http-client.env.json file:

{
  "dev": {
    "HostAddress": "https://dev-api.example.com",
    "ApiKey": "dev-secret"
  },
  "prod": {
    "HostAddress": "https://api.example.com",
    "ApiKey": "prod-secret"
  }
}

Reference these variables in your .http file:

GET {{HostAddress}}/users
Authorization: Bearer {{ApiKey}}

Then run httprunner with the --env flag:

httprunner tests.http --env dev

Generating Environment Files

Manually managing http-client.env.json files can be tedious, especially when dealing with short-lived tokens or multiple environments. I recommend scripting the generation of this file.

Here is an example PowerShell script that fetches access tokens from Azure CLI and generates a comprehensive environment file for localhost, Docker, and development environments:

$management_api_tokens = az account get-access-token `
  --scope app://api.example.net/dev/management_api/.default | ConvertFrom-Json

$simulator_tokens = az account get-access-token `
  --scope app://api.example.net/dev/simulator/.default | ConvertFrom-Json

$environment = @{
  localhost = @{
    authorization = "Bearer " + $management_api_tokens.accessToken
    simulator_authorization = "Bearer " + $simulator_tokens.accessToken
    cpo = "http://localhost:8900"
    simulator = "http://localhost:8901"
    management_api = "http://localhost:8150"
  }
  docker = @{
    authorization = "Bearer " + $management_api_tokens.accessToken
    simulator_authorization = "Bearer " + $simulator_tokens.accessToken
    cpo = "http://host.docker.internal:8900"
    simulator = "http://host.docker.internal:8901"
    management_api = "http://host.docker.internal:8150"
  }
  dev = @{
    authorization = "Bearer " + $management_api_tokens.accessToken
    simulator_authorization = "Bearer " + $simulator_tokens.accessToken
    cpo = "https://ocpi.example.net"
    simulator = "https://ocpi-simulator.example.net"
    management_api = "https://management-api.example.net"
  }
}

Set-Content -Path ./http-client.env.json -Value ($environment | ConvertTo-Json -Depth 10)

Delays

Rate limiting is a common constraint when testing APIs. httprunner allows you to introduce delays either globally or per request. My personal use case would be eventually consistent systems where you want to wait for a certain state before proceeding.

To add a delay between every request in a run, use the CLI flag:

httprunner tests.http --delay 500

For more granular control, use comments in your .http file:

# @pre-delay 1000
# @post-delay 500
GET https://httpbin.org/get
  • @pre-delay: Wait before sending the request (in milliseconds).
  • @post-delay: Wait after the request completes (in milliseconds).

Timeouts

Network conditions can be unpredictable. You can configure timeouts to fail tests if an API is too slow. If you’re testing against a local development server, you might want to set a shorter timeout than the default 30 seconds.

# Wait up to 5 seconds for a response
# @timeout 5000 ms
GET https://api.example.net/delay/2

# Custom connection timeout (default is 30s)
# @connection-timeout 10 s
GET https://api.example.net/get

Supported units include ms, s, and m.

Request Chaining

One of the most powerful features for integration testing is chaining requests—using data from a previous response in a subsequent request. httprunner supports extracting values from headers, JSON bodies, and even the original request data.

Request Variable Syntax

The syntax for request variables is:

{{<request_name>.<source>.<part>.<path>}}
  • request_name: The name defined via # @name <name>
  • source: request or response
  • part: body or headers
  • path: The specific value to extract

Extraction Patterns

  • JSON Bodies: Use JSONPath syntax (e.g., $.user.id, $.items[0].name).
  • Headers: Use the header name (e.g., Content-Type, Location).
  • Full Body: Use * to extract the entire body.

Example Scenario

# @name create_user
POST https://api.example.com/users
Content-Type: application/json

{
  "username": "test_user",
  "role": "admin"
}

###

# @name login
POST https://api.example.com/login
Content-Type: application/json

{
  "username": "{{create_user.request.body.$.username}}",
  "password": "default_password"
}

###

# Use the token from login response
GET https://api.example.com/admin/dashboard
Authorization: Bearer {{login.response.body.$.token}}
X-User-Role: {{create_user.request.body.$.role}}

Conditional Execution

Complex test scenarios often require conditional logic. You might want to skip a cleanup step if the creation failed, or run specific tests only if a feature flag is enabled.

httprunner provides @dependsOn and @if directives.

# @name create_user
POST https://api.example.com/users
...

###

# Only run if create_user succeeded (HTTP 2xx)
# @dependsOn create_user
POST https://api.example.com/users/{{create_user.response.body.$.id}}/activate

###

# Only run if the user status is "active"
# @if create_user.response.body.$.status active
GET https://api.example.com/users/{{create_user.response.body.$.id}}

Assertions

No test is complete without verification. httprunner allows you to assert response status, headers, and body content directly in your .http file.

Basic Assertions

GET https://httpbin.org/json

EXPECTED_RESPONSE_STATUS 200
EXPECTED_RESPONSE_HEADERS "Content-Type: application/json"
EXPECTED_RESPONSE_BODY "slideshow"

Variable Substitution in Assertions

Crucially, variables are fully supported in assertions, allowing you to validate that a response matches input parameters or previous request data.

@expected_status=200
@user_id=123

# @name create_user
POST https://api.example.com/users
{ "id": "{{user_id}}" }

###

GET https://api.example.com/users/{{user_id}}

EXPECTED_RESPONSE_STATUS {{expected_status}}
EXPECTED_RESPONSE_BODY "{{user_id}}"
EXPECTED_RESPONSE_HEADERS "Location: /users/{{user_id}}"

This makes dynamic testing significantly easier, as you don’t need to hardcode expected values.

If any assertion fails, httprunner will report the test as failed and exit with a non-zero status code, which is essential for CI/CD pipelines.

Note: For requests that have assertions that expect failed responses (e.g., testing error handling), you can use EXPECTED_RESPONSE_STATUS to specify the expected failure status code (like 400 or 404). The “failed” request will no longer be flagged as failed, but instead will be validated against the expected status code and assertions.

Verbose Mode

When developing or debugging, you often need more insight into what httprunner is doing.

Use the --verbose flag to see detailed request and response information, including full headers and bodies. Add --pretty-json to format JSON payloads for readability.

httprunner tests.http --verbose --pretty-json

Discovery Mode

If you have tests scattered across multiple directories, use --discover to recursively find and execute all .http files.

httprunner --discover --verbose

Report Generation and Logging

For long-running tests or CI pipelines, console output isn’t enough. httprunner can generate structured reports and detailed logs.

Reports

Generate summary reports in Markdown (default) or HTML using the --report flag. HTML reports include responsive styling and dark mode support.

# Generate Markdown report
httprunner tests.http --report

# Generate HTML report
httprunner tests.http --report html

Logging

Use --log to save all console output to a file. This is useful for auditing and post-execution analysis.

httprunner tests.http --log execution.log

Export Mode

Sometimes you need the raw HTTP data for documentation or manual replay. The --export flag saves every request and response to individual, timestamped files.

httprunner tests.http --export

This will create files like GET_users_request_1738016400.log and GET_users_response_1738016400.log containing the exact payload sent and received.

Insecure HTTPS

When testing against local development environments or staging servers with self-signed certificates, SSL validation can be a blocker. Use --insecure to bypass these checks.

httprunner https://localhost:5001/api/tests.http --insecure

Note: Only use this in trusted environments!

Telemetry

httprunner collects anonymous usage data to help improve the tool. If you prefer to opt-out, you can disable it via a flag or environment variable.

# Disable via CLI
httprunner tests.http --no-telemetry

# Disable via Environment Variable
export HTTPRUNNER_TELEMETRY_OPTOUT=1
# or
export DO_NOT_TRACK=1

CI/CD Integration

Automating these tests in a CI/CD pipeline is straightforward. Since httprunner is available as a Docker image and a standalone binary, it fits easily into GitHub Actions or GitLab CI.

Here is an example GitHub Actions workflow:

name: Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install HTTP Runner
        run: snap install httprunner

      - name: Run API Tests
        run: tests/api.http --env staging --report markdown

Or, if you prefer installing the binary:

- name: Install HTTP Runner
  run: curl -fsSL https://christianhelle.com/httprunner/install | bash

- name: Run Tests
  run: httprunner tests/*.http --env staging --report html

This setup ensures that every change is validated against your API, providing fast feedback on regressions.

Conclusion

By combining the simplicity of .http files with the advanced features of httprunner, you can build a robust integration testing suite that lives right alongside your code. It’s version-controlled, easy to read, and powerful enough for complex scenarios involving authentication, chaining, and conditional logic.

Give it a try and let me know what you think!