Karate: A Complete Testing Guide for Developers
Karate is an open-source unified testing framework that combines API testing, mocking, performance testing, and UI automation into a single, cohesive toolset. Originally designed for HTTP API testing, Karate has evolved to support a wide range of protocols including gRPC, GraphQL, SOAP, and even browser automation through WebDriver integration. Unlike traditional testing frameworks that require extensive programming knowledge, Karate uses a domain-specific language (DSL) written in plain text Gherkin-style syntax, making tests readable, maintainable, and accessible to both technical and non-technical team members.
What Makes Karate Different
Traditional API testing tools like Postman, REST-assured, or Supertest require developers to write significant amounts of boilerplate code for assertions, JSON parsing, and HTTP request construction. Karate eliminates this overhead by providing built-in capabilities for:
- HTTP request/response handling without explicit JSON parsing
- Schema validation with native JSON Schema support
- Parallel test execution baked directly into the framework
- Service virtualization for mocking dependent services
- Performance testing using Gatling integration
- UI automation through WebDriver and Chrome DevTools Protocol
Core Architecture
Karate's architecture is built on a Java-based test runner that interprets feature files written in Gherkin syntax. Each feature file contains scenarios that describe test steps using Karate's expressive DSL. The framework handles all the underlying complexity of HTTP communication, JSON manipulation, and assertion logic, allowing developers to focus on what they're testing rather than how they're testing it.
The key components of Karate's architecture include:
- Feature Files: Plain text files with .feature extension containing test scenarios
- JavaScript Engine: Nashorn/GraalVM-based engine for scripting and expressions
- HTTP Client: Built on Apache HTTP Client with connection pooling and proxy support
- Assertion Engine: Integrated matchers for JSON, XML, and plain text responses
- Report Generator: Cucumber-compatible HTML reports with detailed request/response logs
Why Karate Matters for Modern Development
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Reducing Test Maintenance Burden
In microservices architectures, the number of API endpoints multiplies rapidly. Each endpoint requires tests, and traditional approaches lead to massive codebases of test utilities, helpers, and assertion libraries. Karate collapses this complexity by providing a declarative syntax where HTTP requests, response validation, and assertions coexist in a single readable file. A typical Karate test for a REST endpoint requires 5-10 lines of DSL compared to 30-50 lines in REST-assured or equivalent Java code.
Unified Testing Stack
Most organizations maintain separate tools for API testing, performance testing, and UI automation. This fragmentation creates knowledge silos, inconsistent reporting, and duplicated effort. Karate provides a single framework that handles all three domains, allowing teams to share test scenarios, reuse request patterns, and maintain a single reporting pipeline. The same feature file that validates an API response can be extended to include performance assertions or trigger UI validations.
Built-in Parallel Execution
Karate includes native support for parallel test execution without requiring external test runners or complex configuration. By simply setting the thread count in the runner class, developers can execute hundreds of API tests simultaneously. This is particularly valuable in CI/CD pipelines where fast feedback is critical. The framework handles thread safety automatically, ensuring that cookies, headers, and authentication tokens are properly isolated per thread.
Service Virtualization and Mocking
Karate's mocking capabilities allow teams to simulate dependent services with minimal effort. Instead of maintaining separate mock servers or complex WireMock configurations, developers can create mock feature files that define request matching rules and response templates. These mocks can run as standalone servers or be embedded within test suites, enabling true isolated testing of microservices.
Getting Started with Karate
Project Setup
For a Maven project, add the following dependency to your pom.xml:
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-core</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-junit5</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
For Gradle, add to build.gradle:
testImplementation 'com.intuit.karate:karate-core:1.5.1'
testImplementation 'com.intuit.karate:karate-junit5:1.5.1'
Project Structure Convention
Karate follows a convention-based structure that makes test organization straightforward:
src/
test/
java/
examples/
ExamplesTest.java # Test runner
resources/
examples/
example.feature # Feature file
karate-config.js # Global configuration
Feature files live in the resources directory alongside their corresponding Java runner classes. The karate-config.js file in the resources root provides global configuration that applies to all tests.
Writing Your First Test
Create a feature file named first-test.feature in your test resources:
Feature: Sample API Tests
Background:
* url 'https://jsonplaceholder.typicode.com'
Scenario: Get a single post and validate response
Given path '/posts/1'
When method GET
Then status 200
And match response == { id: 1, userId: '#number', title: '#string', body: '#string' }
And match response.id == 1
And match response.title contains 'sunt'
Scenario: Create a new resource with a JSON body
Given path '/posts'
And request {
title: 'Karate Test Post',
body: 'This is a test body',
userId: 1
}
When method POST
Then status 201
And match response contains { id: '#number', title: 'Karate Test Post' }
Create a JUnit 5 test runner class:
package examples;
import com.intuit.karate.junit5.Karate;
public class FirstTestRunner {
@Karate.Test
Karate testAll() {
return Karate.run("first-test").relativeTo(getClass());
}
}
Karate Configuration File
The karate-config.js file allows you to define reusable variables, environment-specific settings, and global hooks:
function fn() {
var env = karate.env || 'dev';
var config = {
baseUrl: 'https://jsonplaceholder.typicode.com',
timeout: 5000,
retryCount: 3
};
if (env === 'staging') {
config.baseUrl = 'https://staging-api.example.com';
config.timeout = 10000;
}
if (env === 'production') {
config.baseUrl = 'https://api.example.com';
config.timeout = 15000;
config.retryCount = 5;
}
karate.configure('connectTimeout', 5000);
karate.configure('readTimeout', config.timeout);
return config;
}
The configuration function runs before each scenario, and the returned object's properties become available as variables in all feature files. The karate.env property can be set via system property or command line argument to switch between environments.
Core Testing Capabilities
HTTP Request Handling
Karate provides a fluent DSL for constructing HTTP requests with full control over headers, query parameters, form fields, and request bodies:
Scenario: Complete HTTP request demonstration
Given url 'https://httpbin.org/anything'
And headers { Authorization: 'Bearer token123', Accept: 'application/json' }
And param page = 1
And param limit = 10
And request {
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true
}
}
When method POST
Then status 200
And match response.json.name == 'John Doe'
Response Validation and Matching
Karate's matching engine is one of its most powerful features. It supports exact matching, partial matching, fuzzy matching with placeholders, and schema validation:
Scenario: Advanced response matching techniques
Given url 'https://jsonplaceholder.typicode.com/users/1'
When method GET
Then status 200
# Exact match
And match response.id == 1
# Type placeholders - validates type but not value
And match response == {
id: '#number',
name: '#string',
email: '#string',
address: '#object',
company: '#object'
}
# Partial match - only checks specified fields
And match response contains {
username: 'Bret',
email: '#string',
address: { city: '#string', zipcode: '#string' }
}
# Array matching with each
And match response.address.geo.lat == '#number'
# Null checking
And match response.website != null
JSON Schema Validation
Karate supports JSON Schema validation natively. You can define schemas inline or reference external schema files:
Scenario: JSON Schema validation
Given url 'https://jsonplaceholder.typicode.com/users/1'
When method GET
Then status 200
# Inline schema validation
And match response == """
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
},
"required": ["id", "name", "email"]
}
"""
Reusing Requests with Call and Karate Expressions
Karate enables you to define reusable JavaScript functions for common operations and call other feature files as subroutines:
Feature: Reusable authentication module
Scenario: Generate authentication token
Given url authServiceUrl
And request { username: username, password: password }
When method POST
Then status 200
And def authToken = response.token
To call this reusable scenario from another feature file:
Scenario: Use shared authentication
* def authResult = call read('classpath:auth/login.feature') { username: 'testuser', password: 'testpass' }
* def authToken = authResult.authToken
Given url apiBaseUrl
And headers { Authorization: 'Bearer ' + authToken }
Given path '/protected-resource'
When method GET
Then status 200
Data-Driven Testing with Examples
Karate supports data-driven testing through scenario outlines with examples tables:
Scenario Outline: Validate multiple user accounts
Given url 'https://jsonplaceholder.typicode.com'
Given path '/users/' + userId
When method GET
Then status 200
And match response.id == userId
And match response.email == expectedEmail
Examples:
| userId | expectedEmail |
| 1 | 'Sincere@april.biz' |
| 2 | 'Shanna@melissa.tv' |
| 3 | 'Nathan@yesenia.net' |
| 4 | 'Julianne.OConner@kory.org' |
Handling Complex JSON Transformations
Karate's JavaScript engine allows powerful data transformations between API calls:
Scenario: Extract and transform data between requests
Given url 'https://jsonplaceholder.typicode.com/users'
When method GET
Then status 200
# Extract specific fields into a new array
* def userSummaries = []
* def extractSummary = function(user) {
return { name: user.name, city: user.address.city }
}
* def userSummaries = karate.map(response, extractSummary)
# Use transformed data in subsequent request
Given url 'https://httpbin.org/anything'
And request { summaries: userSummaries, count: response.length }
When method POST
Then status 200
Mocking and Service Virtualization
Creating Mock Services
Karate allows you to create mock servers from feature files. These mocks can match incoming requests based on path, method, headers, and body content, and return predefined responses:
Feature: Mock user service
Background:
* configure mock = true
Scenario: Mock GET user by ID
* def expectedId = request.pathParams.id
When method GET
Then match request.method == 'GET'
And match request.headers['Authorization'] == 'Bearer mock-token'
* def response = """
{
"id": "#(expectedId)",
"name": "Mock User",
"email": "mock@example.com",
"status": "active"
}
"""
* def responseStatus = 200
Scenario: Mock user creation
When method POST
Then match request.body contains { username: '#string' }
* def response = """
{
"id": 999,
"username": "#(request.body.username)",
"created": true,
"timestamp": "2024-01-01T00:00:00Z"
}
"""
* def responseStatus = 201
Scenario: Mock user not found
Given path '/users/999'
When method GET
Then match request.method == 'GET'
* def response = { error: 'User not found', code: 404 }
* def responseStatus = 404
To start the mock server programmatically:
package examples;
import com.intuit.karate.MockServer;
import com.intuit.karate.junit5.Karate;
public class MockServerRunner {
public static void main(String[] args) {
MockServer server = MockServer
.feature("classpath:mocks/user-mock.feature")
.http(8080)
.build();
server.start();
System.out.println("Mock server started on port 8080");
}
}
Using Mocks in Tests
Mocks can be started inline within test scenarios, making integration tests self-contained:
Scenario: Test with embedded mock server
* def mockServer = karate.start('classpath:mocks/payment-gateway-mock.feature')
* def paymentServiceUrl = 'http://localhost:' + mockServer.port
Given url paymentServiceUrl
Given path '/process-payment'
And request { amount: 100, currency: 'USD', cardToken: 'tok_visa' }
When method POST
Then status 200
And match response.status == 'succeeded'
* karate.stop(mockServer.port)
Performance Testing with Gatling
Karate-Gatling Integration
Karate integrates seamlessly with Gatling, a powerful performance testing tool. Feature files can be reused as performance test scenarios without modification:
package examples;
import com.intuit.karate.gatling.PreDef;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
public class PerformanceSimulation extends Simulation {
{
HttpProtocolBuilder httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json");
ScenarioBuilder apiLoadTest = scenario("API Load Test")
.exec(karateFeature("classpath:examples/api-tests.feature"));
setUp(
apiLoadTest.injectOpen(
rampUsers(50).during(Duration.ofSeconds(30)),
constantUsersPerSec(10).during(Duration.ofMinutes(2))
)
).protocols(httpProtocol);
}
}
UI Automation
Browser Testing with Karate
Karate's UI automation module leverages WebDriver and Chrome DevTools Protocol for browser testing. Tests are written in the same declarative style as API tests:
Feature: Web application login test
Background:
* configure driver = { type: 'chrome', headless: false }
* driver 'https://example.com/login'
Scenario: Successful login flow
Given driver.input('#username', 'testuser@example.com')
And driver.input('#password', 'SecurePass123!')
When driver.click('#login-button')
Then driver.waitFor('#dashboard-container', 5000)
And driver.text('#welcome-message') contains 'Welcome back'
And driver.exists('#user-profile-menu')
# Validate API call triggered by login
* driver.intercept = true
* driver.click('#logout-button')
And match driver.interceptedRequests contains { url: '#string', method: 'POST' }
Hybrid API and UI Testing
One of Karate's most powerful capabilities is combining API and UI tests within the same scenario. This enables end-to-end validation where API calls set up test data and UI tests validate the user experience:
Scenario: End-to-end order placement
# Step 1: Create order via API
Given url 'https://api.example.com'
Given path '/orders'
And request {
items: [{ productId: 'SKU123', quantity: 2 }],
shippingAddress: { street: '123 Main St', city: 'Testville' }
}
When method POST
Then status 201
* def orderId = response.orderId
# Step 2: Verify order appears in UI
* driver 'https://app.example.com/orders/' + orderId
And driver.waitFor('.order-details', 5000)
And driver.text('.order-status') == 'Created'
And driver.text('.order-id') == orderId
# Step 3: Update order status via API
Given path '/orders/' + orderId + '/status'
And request { status: 'shipped', trackingNumber: 'TRK789' }
When method PUT
Then status 200
# Step 4: Verify status reflected in UI
* driver.refresh()
And driver.waitFor('.order-status.shipped', 5000)
And driver.text('.tracking-number') == 'TRK789'
Advanced Features and Patterns
Conditional Logic and Dynamic Scenarios
Karate's JavaScript engine enables conditional test execution and dynamic scenario generation:
Scenario: Conditional API validation based on user role
Given url 'https://api.example.com'
Given path '/users/me'
When method GET
Then status 200
* def validateAdminFields = function() {
karate.log('Validating admin-specific fields');
match response.permissions != null;
match response.permissions.length > 0;
match response.adminSince == '#string';
}
* def validateUserFields = function() {
karate.log('Validating standard user fields');
match response.preferences == '#object';
}
# Execute different validations based on role
* if (response.role == 'admin') karate.call(validateAdminFields)
* if (response.role != 'admin') karate.call(validateUserFields)
Retry Logic and Resilience Testing
Karate provides built-in retry capabilities for handling flaky services and testing resilience patterns:
Scenario: Retry on transient failures
* configure retry = { count: 3, interval: 2000 }
Given url 'https://api.example.com/health'
When method GET
Then status 200
And match response.status == 'healthy'
# Retry with custom condition
* def isValid = function(response) {
return response.status == 200 && response.json.status == 'ready'
}
* retry until isValid
GraphQL Testing
Karate supports GraphQL APIs with the same declarative syntax used for REST:
Scenario: GraphQL query with variables
Given url 'https://graphql.example.com/graphql'
And headers { Authorization: 'Bearer ' + authToken }
And request """
{
"query": "query GetUser($id: ID!) { user(id: $id) { name email posts { title } } }",
"variables": { "id": "123" }
}
"""
When method POST
Then status 200
And match response.data.user.name == '#string'
And match response.data.user.posts == '#array'
And match each response.data.user.posts contains { title: '#string' }
SOAP and XML Testing
Karate handles XML-based SOAP services naturally:
Scenario: SOAP service invocation
Given url 'https://soap.example.com/WeatherService'
And headers { SOAPAction: 'GetWeather' }
And request """
NYC
2024-12-25
"""
When method POST
Then status 200
And match /Envelope/Body/GetWeatherResponse/Temperature == '#number'
And match /Envelope/Body/GetWeatherResponse/Conditions contains 'Sunny'
Best Practices for Karate Testing
Organize Feature Files by Domain
Structure feature files around business domains rather than technical endpoints. Group related scenarios in feature files that represent complete workflows:
src/test/resources/
auth/
login.feature
registration.feature
password-reset.feature
orders/
create-order.feature
order-lifecycle.feature
order-search.feature
users/
user-profile.feature
user-preferences.feature
shared/
auth-helper.feature
data-cleanup.feature
Leverage the Background Section
Use the Background section to set up common prerequisites that apply to all scenarios in a feature file. This reduces repetition and improves maintainability:
Feature: Order Management API
Background:
* url baseUrl
* configure headers = { Authorization: 'Bearer ' + authToken }
* def authToken = call read('classpath:shared/auth-helper.feature')
* configure retry = { count: 2, interval: 1000 }
Implement Proper Environment Management
Create a comprehensive karate-config.js that handles multiple environments, secrets management, and dynamic configuration:
function fn() {
var env = karate.env || 'local';
var config = {};
// Base configuration
config.timeout = 5000;
config.retryCount = 3;
// Environment-specific overrides
if (env === 'local') {
config.baseUrl = 'http://localhost:8080';
config.authUrl = 'http://localhost:8081';
config.mockExternalServices = true;
} else if (env === 'ci') {
config.baseUrl = 'http://test-container:8080';
config.authUrl = 'http://auth-container:8081';
config.mockExternalServices = true;
} else if (env === 'staging') {
config.baseUrl = 'https://staging-api.example.com';
config.authUrl = 'https://staging-auth.example.com';
config.mockExternalServices = false;
}
// Secrets from environment variables
config.apiKey = karate.properties['api.key'] || java.lang.System.getenv('API_KEY');
return config;
}
Implement Custom Reporting
Enhance Karate's built-in reporting with custom hooks and listeners:
package utils;
import com.intuit.karate.RuntimeHook;
import com.intuit.karate.Suite;
public class CustomReportHook implements RuntimeHook {
@Override
public boolean beforeScenario(Scenario scenario) {
scenario.getTags().forEach(tag -> {
if (tag.equals("critical")) {
scenario.getLogger().info("Starting critical scenario: " + scenario.getName());
}
});
return true;
}
@Override
public void afterScenario(Scenario scenario) {
if (scenario.isFailed()) {
scenario.getLogger().error("Scenario failed: " + scenario.getName());
// Trigger alert or notification
sendFailureNotification(scenario);
}
}
}
Handle Authentication Patterns
Centralize authentication logic in reusable feature files to avoid duplicating login steps across tests:
Feature: OAuth2 authentication helper
Scenario: Obtain access token via client credentials
Given url authUrl
Given path '/oauth/token'
And headers { Content-Type: 'application/x-www-form-urlencoded' }
And form field grant_type = 'client_credentials'
And form field client_id = clientId
And form field client_secret = clientSecret
When method POST
Then status 200
* def accessToken = response.access_token
* def tokenType = response.token_type
Scenario: Obtain token via password grant
Given url authUrl
Given path '/oauth/token'
And form field grant_type = 'password'
And form field username = username
And form field password = password
When method POST
Then status 200
* def accessToken = response.access_token
Use Tags for Test Filtering
Apply tags to scenarios for selective test execution in different pipeline stages:
@smoke @critical @api
Feature: Critical payment endpoints
@smoke @positive
Scenario: Successful payment processing
Given url paymentServiceUrl
# ...
@integration @slow
Scenario: End-to-end payment with fraud detection
Given url paymentServiceUrl
# ...
@negative @edge-case
Scenario: Payment with expired card
Given url paymentServiceUrl
# ...
Run specific tag subsets from the runner:
* def result = Karate.run('classpath:payments/').tags('@smoke').parallel(5)
Manage Test Data Carefully
Use Karate's embedded expressions and JavaScript functions to generate dynamic test data rather than hardcoding values:
Scenario: Create user with dynamic data
* def randomSuffix = Math.floor(Math.random() * 10000)
* def testEmail = 'user_' + randomSuffix + '@example.com'
* def testUsername = 'testuser_' + randomSuffix
Given url baseUrl
Given path '/users'
And request {
username: testUsername,
email: testEmail,
name: 'Test User ' + randomSuffix,
metadata: {
createdAt: karate.timestamp(),
source: 'automated-test'
}
}
When method POST
Then status 201
Implement Proper Cleanup
Design tests to clean up after themselves, especially in shared environments:
Feature: Resource lifecycle management
Background:
* url baseUrl
* def createdResources = []
Scenario: Create and verify resource
Given path '/resources'
And request { name: 'test-resource', type: 'temporary' }
When method POST
Then status 201
* def resourceId = response.id
* createdResources.push(resourceId)
# Verify resource
Given path '/resources/' + resourceId
When method GET
Then status 200
# Cleanup
Given path '/resources/' + resourceId
When method DELETE
Then status 204
Continuous Integration Integration
Maven Surefire Configuration
Configure Maven to run Karate tests during the integration-test phase:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Runner.java</include>
</includes>
<systemPropertyVariables>
<karate.env>ci</karate.env>
</systemPropertyVariables>
</configuration>
</plugin>
Jenkins Pipeline Integration
pipeline {
agent any
stages {
stage('Karate API Tests') {
steps {
sh 'mvn test -Dkarate.env=ci -Dtest=TestParallel'
}
post {
always {
publishHTML(
target: [
allowMissing: false,
always: true,
reportDir: 'target/karate-reports',
reportName: 'Karate Test Report'
]
)
}
}
}
}
}
Debugging and Troubleshooting
Enabling Detailed Logging
Karate provides comprehensive logging options for debugging test failures:
# In karate-config.js
karate.configure('logPrettyRequest', true);
karate.configure('logPrettyResponse', true);
# Or per scenario
Scenario: Debug with verbose logging
* configure logPrettyRequest = true
* configure logPrettyResponse = true
Given url 'https://api.example.com/endpoint'
When method GET
Then status 200
Using the Karate IDE Debugger
For complex scenarios, use the Karate IntelliJ plugin or VS Code extension to set breakpoints and step through feature files line by line. The debugger shows variable values, request/response bodies, and allows expression evaluation at each step.
Common Issues and Solutions
- Connection refused: Verify the target service is running and the URL is correct. Check firewall rules and proxy settings.
- SSL certificate errors: Use
karate.configure('ssl', true)to enable relaxed SSL validation in non-production environments. - Variable not found: Ensure variables are defined with
defbefore use. Check scope—variables defined in one scenario are not available in another. - Timeout failures: Increase timeouts via
karate.configure('readTimeout', 30000)or adjust retry configuration.
Conclusion
Karate represents a significant evolution in how development teams approach testing. By unifying API testing, mocking, performance testing, and UI automation under a single declarative framework, it eliminates the traditional fragmentation that slows down delivery pipelines. The framework's design philosophy—prioritizing readability, reducing boilerplate, and enabling parallel execution—aligns perfectly with modern microservices architectures and continuous delivery requirements.
Teams adopting Karate consistently report reduced test maintenance overhead, faster test creation cycles, and broader test coverage across their service ecosystems. The ability to reuse the same feature files for functional validation, performance benchmarking, and integration testing creates a single source of truth for service behavior expectations. As software systems continue to grow in complexity, tools like Karate that simplify testing while expanding capability will become increasingly essential to maintaining delivery velocity and system reliability.