A powerful C# CLI application for comprehensive API testing and mocking, enabling developers to streamline API validation and development workflows with rich configuration and execution capabilities.
- Comprehensive API Testing: Define and run detailed API tests.
- Multiple Request Methods: Support for GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
- Rich Payload Types: JSON, Text, Form Data.
- File Upload Support: Test multipart/form-data requests with file uploads.
- Detailed Assertions: Validate response status, headers, body content (JSON properties, arrays, values), and response time.
- Integrated Mock Server: Simulate API endpoints for development and testing.
- Dynamic & Conditional Responses: Define mock responses based on request parameters, headers, or body content.
- Template Variables: Use built-in (random data, timestamps) and custom (request-derived) variables in mock responses.
- File-based Configuration: Manage mock definitions in simple
.mock.json
files.
- Environment Management: Use different configurations for development, staging, production, etc.
- Variable Overriding: Project, Environment, and Request-level variable support with clear precedence.
- User-Friendly CLI:
- Interactive Creation: Commands to interactively create test and mock definitions.
- Detailed Reports: Comprehensive output with request, response, and assertion details.
- Visual Progress Indicators: Animated progress display for running multiple tests.
- Deployment & Extensibility:
- Single File Deployment: Simplified deployment as a single executable file.
- .NET 8.0 & .NET 9 Ready: Built with .NET 8.0, with automatic multi-targeting for .NET 9.0 when available.
- .NET 8.0 SDK (required)
- .NET 9.0 SDK (optional, for building with .NET 9.0 when available)
# Clone the repository
git clone https://github.com/nahid/apify.git # Replace with actual URL
cd apify
# Build the project
dotnet build
# Run the application (from the project directory)
dotnet run -- [command] [options]
# Example:
dotnet run -- --help
dotnet run -- init --name "My API Project" --base-url "https://api.example.com"
For global installation or easier access, build as a single file and place it in your PATH:
For simplified deployment, you can build a single executable file:
# Build a single file executable for your platform (e.g., linux-x64)
dotnet publish -c Release -r linux-x64 --self-contained true
# Windows:
# dotnet publish -c Release -r win-x64 --self-contained true
# macOS (Intel):
# dotnet publish -c Release -r osx-x64 --self-contained true
# macOS (ARM64):
# dotnet publish -c Release -r osx-arm64 --self-contained true
# Then, you can copy the executable (e.g., ./bin/Release/net8.0/linux-x64/publish/apify)
# to a directory in your PATH, for example /usr/local/bin/
# cp ./bin/Release/net8.0/linux-x64/publish/apify /usr/local/bin/apify
# After this, you can run 'apify' directly from any terminal.
To start using Apify in your project, navigate to your project's root directory and run:
apify init --name "My Project API Tests" --base-url "https://api.myproject.com"
This command interactively prompts for:
- Project Name
- Base URL for your API
- Default Environment Name (e.g., "Development")
- Additional environments (e.g., "Staging,Production")
It creates:
apify-config.json
: The main configuration file..apify/
: A directory to store your API test definitions (.json
) and mock definitions (.mock.json
).- Sample API test and mock definition files within
.apify/apis/
and.apify/mocks/
respectively. - A
MockServer
configuration block inapify-config.json
.
This file stores project-level settings, environments, and mock server configuration.
{
"Name": "My Project API Tests",
"Description": "API Tests for My Project",
"DefaultEnvironment": "Development",
"Variables": {
"globalProjectVar": "This variable is available in all environments and tests"
},
"Environments": [
{
"Name": "Development",
"Description": "Development environment specific variables",
"Variables": {
"baseUrl": "https://dev-api.myproject.com",
"apiKey": "dev-secret-key"
}
},
{
"Name": "Production",
"Description": "Production environment specific variables",
"Variables": {
"baseUrl": "https://api.myproject.com",
"apiKey": "prod-secret-key"
}
}
],
"MockServer": {
"Port": 8080,
"Directory": ".apify/mocks",
"Verbose": false,
"EnableCors": true,
"DefaultHeaders": {
"X-Mock-Server": "Apify"
},
"FileStoragePath": ".apify/uploads/mock-server"
}
}
Name
,Description
: Project metadata.DefaultEnvironment
: The environment used if--env
is not specified.Variables
(Project-Level): Key-value pairs available across all tests and environments unless overridden.Environments
: An array of environment objects.Name
: Unique name for the environment (e.g., "Development", "Staging").Variables
: Key-value pairs specific to this environment. These override project-level variables.
MockServer
: Configuration for the mock server.Port
: Port for the mock server.Directory
: Directory containing mock definition files.Verbose
: Enable verbose logging for the mock server.EnableCors
: Enable CORS headers (defaults to allow all).DefaultHeaders
: Headers to be added to all mock responses.FileStoragePath
: Path to store files uploaded to mock endpoints (if configured in the mock definition).
API tests are defined in JSON files (e.g., .apify/users/get-users.json
).
Structure:
{
"Name": "Get All Users",
"Description": "Fetches the list of all users",
"Uri": "{{baseUrl}}/users?page={{defaultPage}}",
"Method": "GET",
"Headers": {
"Accept": "application/json",
"X-Api-Key": "{{apiKey}}"
},
"Payload": null,
"PayloadType": "none", // "json", "text", "formData"
"Files": [], // For "multipart/form-data"
"Timeout": 30000, // Optional, in milliseconds
"Tags": ["users", "smoke"], // Optional, for filtering tests
"Variables": { // Request-specific variables (highest precedence)
"defaultPage": "1"
},
"Tests": [
{
"Name": "Status code is 200 OK",
"AssertType": "StatusCode",
"ExpectedValue": "200"
},
{
"Name": "Response body is an array",
"AssertType": "IsArray",
"Property": "data" // Path to property, e.g., "user.details.name" or "users[0].id"
},
{
"Name": "First user has an ID",
"AssertType": "ContainsProperty",
"Property": "data[0].id"
},
{
"Name": "Content-Type header is application/json",
"AssertType": "HeaderContains",
"Property": "Content-Type", // Header name
"ExpectedValue": "application/json"
},
{
"Name": "Response time is below 500ms",
"AssertType": "ResponseTimeBelow",
"ExpectedValue": "500" // Milliseconds
},
{
"Name": "User count matches expected",
"AssertType": "Equal",
"Property": "meta.total_items",
"ExpectedValue": "25" // Expected value (can be string, number, boolean)
},
{
"Name": "User list is not empty",
"AssertType": "ArrayNotEmpty",
"Property": "data"
}
]
}
Variables
(Request-Level): Override environment and project variables.Tags
: Used for filtering tests withapify tests --tag <tagname>
.Tests
(Assertions):AssertType
: The type of assertion. Supported types:StatusCode
: Checks the HTTP status code.ExpectedValue
is the code (e.g., "200", "4xx").ContainsProperty
: Checks if a JSON property exists.Property
is the JSONPath-like path.HeaderContains
: Checks if a response header contains a value.Property
is header name,ExpectedValue
is the content.ResponseTimeBelow
: Checks if response time is belowExpectedValue
(ms).Equal
: Checks if a JSON property's value equalsExpectedValue
.Property
is the JSONPath-like path.IsArray
: Checks if a JSON property is an array.Property
is the JSONPath-like path.ArrayNotEmpty
: Checks if a JSON array property is not empty.Property
is the JSONPath-like path.
Property
: For JSON assertions, this is a JSONPath-like expression (e.g.,user.id
,items[0].name
,response.data.info
). For header assertions, it's the header name.ExpectedValue
: The value to assert against.
Mock APIs are defined in .mock.json
files (e.g., .apify/mocks/users/get-user-by-id.mock.json
). Apify uses AdvancedMockApiDefinition
.
Structure (AdvancedMockApiDefinition
):
{
"Name": "Mock User by ID",
"Method": "GET",
"Endpoint": "/api/users/:id", // Path parameters with :param or {param}
"Responses": [
{
"Condition": "path.id == \"1\"", // JavaScript-like condition
"StatusCode": 200,
"ContentType": "application/json",
"Headers": {
"X-Source": "Mock-Conditional-User1"
},
"ResponseTemplate": {
"id": 1,
"name": "John Doe (Mocked)",
"email": "john.mock@example.com",
"requested_id": "{{path.id}}",
"random_code": "{{$random:int:1000:9999}}"
}
},
{
"Condition": "query.type == \"admin\" && header.X-Admin-Token == \"SUPER_SECRET\"",
"StatusCode": 200,
"ResponseTemplate": {
"id": "{{path.id}}",
"name": "Admin User (Mocked)",
"email": "admin.mock@example.com",
"role": "admin",
"token_used": "{{header.X-Admin-Token}}",
"uuid": "{{$random:uuid}}"
}
},
{
"Condition": "body.status == \"pending\"", // Example for POST/PUT
"StatusCode": 202,
"ResponseTemplate": {
"message": "Request for user {{path.id}} with status 'pending' accepted.",
"received_payload": "{{body}}" // Full request body
}
},
{
"Condition": "true", // Default response if no other conditions match
"StatusCode": 404,
"ResponseTemplate": {
"error": "User not found",
"id_searched": "{{path.id}}"
}
}
]
}
Endpoint
: The URL path for the mock. Supports path parameters like/users/:id
or/users/{id}
.Responses
: An array of conditional response objects. They are evaluated in order.Condition
: A JavaScript-like expression to determine if this response should be used.- Access request data:
path.paramName
(e.g.,path.id
)query.paramName
(e.g.,query.page
)header.HeaderName
(e.g.,header.Authorization
, case-insensitive)body.fieldName
(e.g.,body.username
, for JSON bodies)
true
can be used for a default fallback response.
- Access request data:
StatusCode
: The HTTP status code to return.ContentType
: TheContent-Type
header for the response.Headers
: An object of response headers.ResponseTemplate
: The body of the response. Can be a JSON object or a string.- Template Variables:
{{path.paramName}}
: Value of a path parameter.{{query.paramName}}
: Value of a query parameter.{{header.HeaderName}}
: Value of a request header (case-insensitive).{{body.fieldName}}
: Value of a field from the JSON request body.{{body}}
: The full raw request body (string).{{$random:int:min:max}}
: A random integer between min and max (inclusive). E.g.,{{$random:int:1:100}}
.{{$random:uuid}}
: A random UUID.{{timestamp}}
: Current Unix timestamp (seconds).{{datetime}}
: Current datetime in ISO 8601 format.- Any environment or project variable (e.g.,
{{baseUrl}}
).
- Template Variables:
Apify commands are run as apify <command> [subcommand] [options]
if installed globally, or dotnet run -- <command> [subcommand] [options]
if run from the project directory.
Initializes a new API testing project in the current directory.
apify init --name "My API" --base-url "https://api.example.com" [--environment "CustomDev"] [--force]
--name
: (Required) The name of the API testing project.--base-url
: (Required) The base URL for API endpoints.--environment
: The name for the default environment (default: "Development").--force
: Overwrite existingapify-config.json
and.apify
directory if they exist.- Prompts for project name, base URL, default environment name, and other environments to create.
- Creates
apify-config.json
,.apify/
directory with sample test and mock files.
Interactively creates a new API test definition file.
apify create request --file users.getById [--force]
--file
: (Required) The file path for the new API request definition (e.g.,users.all
becomes.apify/users/all.json
). The.json
extension is added automatically.--force
: Overwrite if the file already exists.- Prompts for request name, HTTP method, URI, headers, payload, and basic assertions.
Interactively creates a new mock API definition file.
apify create mock --file mocks.users.getById [--force]
--file
: (Required) The file path for the new mock API definition (e.g.,users.get
becomes.apify/users/get.mock.json
). The.mock.json
extension is added automatically.--force
: Overwrite if the file already exists.- Prompts for mock name, HTTP method, endpoint path, status code, content type, response body, headers, and conditional responses.
Executes API tests from specified definition files.
apify run <file_or_pattern_1> [file_or_pattern_2...] [--env <environment_name>] [--profile <profile_name>] [--verbose]
files
: (Required) One or more API definition file paths or glob patterns (e.g.,users/all.json
,.apify/orders/*.json
). Dot notation likeusers.all
is also supported.--env <environment_name>
: Specifies the environment to use (e.g., "Production"). Uses default fromapify-config.json
if not set.--profile <profile_name>
: Specifies the configuration profile name (default: "Default").--verbose
or-v
: Displays detailed output, including request and response bodies.
Runs all API tests found in the .apify
directory and its subdirectories.
apify tests [--env <environment_name>] [--tag <tag_name>] [--verbose]
--env <environment_name>
: Specifies the environment.--tag <tag_name>
: Filters and runs only tests that have the specified tag in their definition.--verbose
or-v
: Displays detailed output.- Shows visual progress indicators and a summary at the end.
Starts a local API mock server using mock definition files.
apify mock-server [--port <port_number>] [--directory <path_to_mocks>] [--verbose]
--port <port_number>
: Port for the mock server (default: 8080 or as configured inapify-config.json
).--directory <path_to_mocks>
: Directory containing mock definition files (default:.apify/mocks
or as configured).--verbose
or-v
: Enable verbose logging for the mock server.- Reads configuration from the
MockServer
block inapify-config.json
but command-line options take precedence.
Lists all available environments and their variables from apify-config.json
.
apify list-env
These options can be used with most commands.
--debug
: Show detailed debug output, including stack traces and internal logging. Useful for troubleshooting Apify itself.--verbose
or-v
(forrun
,tests
,mock-server
): Enables more detailed output for the specific command.
This project includes GitHub Actions workflows for continuous integration and releases.
The CI workflow (if included in your project, typically in .github/workflows/ci.yml
) runs on every push to the main branch and pull requests. It usually:
- Builds the project.
- Runs API tests (e.g.,
apify tests --env ci
) to verify functionality against a deployed or mocked environment.
To create a release (if configured, typically in .github/workflows/release.yml
):
- Tag-based release: Create and push a new tag:
git tag -a v1.0.0 -m "Version 1.0.0" && git push origin v1.0.0
- Manual release: Via GitHub Actions UI.
This process typically builds single file executables for various platforms and attaches them to a GitHub release.
-
Initialize Project:
apify init --name "User API" --base-url "http://localhost:8080/api" # (Assuming mock server will run on port 8080 for testing against mocks)
-
Create a Mock for
GET /api/users/:id
: Useapify create mock --file users.getById
and define it like this in.apify/mocks/users/getById.mock.json
:{ "Name": "Mock User by ID", "Method": "GET", "Endpoint": "/api/users/:id", "Responses": [ { "Condition": "path.id == \"1\"", "StatusCode": 200, "ContentType": "application/json", "ResponseTemplate": { "id": 1, "name": "Mocked Alice", "email": "alice.mock@example.com" } }, { "Condition": "true", "StatusCode": 404, "ResponseTemplate": { "error": "MockUser not found" } } ] }
-
Start the Mock Server:
apify mock-server --port 8080
(In a separate terminal)
-
Create an API Test for
GET /api/users/1
: Useapify create request --file users.getUser1
and define it in.apify/apis/users/getUser1.json
:{ "Name": "Get User 1", "Uri": "{{baseUrl}}/users/1", // baseUrl will be http://localhost:8080/api "Method": "GET", "Tests": [ { "Name": "Status 200", "AssertType": "StatusCode", "ExpectedValue": "200" }, { "Name": "ID is 1", "AssertType": "Equal", "Property": "id", "ExpectedValue": 1 }, { "Name": "Name is Mocked Alice", "AssertType": "Equal", "Property": "name", "ExpectedValue": "Mocked Alice" } ] }
-
Run the Test:
apify run users.getUser1 --verbose # This will hit your running mock server.
This project is licensed under the MIT License. See the LICENSE file for details. (Ensure you have a LICENSE file).