Mastering API Testing with Karate Framework
Karate framework has revolutionized API testing by combining the simplicity of Gherkin syntax with powerful testing capabilities. Unlike traditional testing frameworks that require extensive programming knowledge, Karate enables both technical and non-technical team members to write comprehensive API tests using natural language.
Why Choose Karate for API Testing?
Key Advantages
- No Programming Required: Write tests in plain English using Gherkin syntax
- Built-in Assertions: Rich set of built-in matchers and assertions
- JSON and XML Support: Native support for modern data formats
- Parallel Execution: Built-in support for parallel test execution
- Mock Server: Create mock services for testing
- Database Testing: Direct database validation capabilities
- CI/CD Integration: Seamless integration with Maven, Gradle, and CI tools
Karate vs Traditional API Testing
| Traditional Tools |
Karate Framework |
| Requires programming skills |
Natural language syntax |
| Separate assertion libraries |
Built-in assertions |
| Complex test data management |
Embedded test data |
| Manual request/response handling |
Automatic handling |
| Separate mocking tools |
Built-in mock server |
Getting Started with Karate
Project Setup
1
2
3
4
5
6
7
8
9
10
11
12
13
| <!-- Maven pom.xml -->
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-junit5</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-apache</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
|
Basic Test Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // KarateTest.java
package com.qahivelab.tests;
import com.intuit.karate.junit5.Karate;
class KarateTest {
@Karate.Test
Karate testUsers() {
return Karate.run("users").relativeTo(getClass());
}
@Karate.Test
Karate testAll() {
return Karate.run("classpath:com/qahivelab/tests");
}
}
|
Writing Your First Karate Test
Simple API Test Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # users.feature
Feature: User Management API
Background:
* url 'https://api.example.com'
* header Accept = 'application/json'
* header Content-Type = 'application/json'
Scenario: Get all users
Given path 'users'
When method GET
Then status 200
And match response == '#array'
And match each response == { id: '#number', name: '#string', email: '#string' }
Scenario: Create a new user
Given path 'users'
And request { name: 'John Doe', email: 'john@example.com', role: 'user' }
When method POST
Then status 201
And match response.id == '#present'
And match response.name == 'John Doe'
And match response.email == 'john@example.com'
|
Advanced Pattern Matching
Karate’s pattern matching capabilities are exceptionally powerful:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| Feature: Advanced Pattern Matching
Scenario: Comprehensive response validation
Given path 'users/1'
When method GET
Then status 200
# Exact match
And match response.name == 'John Doe'
# Type validation
And match response.id == '#number'
And match response.email == '#string'
And match response.active == '#boolean'
# Optional fields
And match response.phone == '#string?'
And match response.address == '#object?'
# Array validation
And match response.roles == '#array'
And match response.roles == '#[3]' # exactly 3 elements
# Regex validation
And match response.email == '#regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
# Custom validation
And match response.createdAt == '#? _ != null && _.length > 0'
|
Healthcare API Testing with Karate
FHIR Compliance Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| Feature: FHIR Patient Resource Testing
Background:
* url 'https://fhir.example.com/R4'
* header Accept = 'application/fhir+json'
* header Content-Type = 'application/fhir+json'
Scenario: Create and validate FHIR Patient resource
Given path 'Patient'
And request
"""
{
"resourceType": "Patient",
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"value": "12345"
}
],
"name": [
{
"use": "official",
"family": "Doe",
"given": ["John", "Michael"]
}
],
"gender": "male",
"birthDate": "1990-01-01"
}
"""
When method POST
Then status 201
# Validate FHIR resource structure
And match response.resourceType == 'Patient'
And match response.id == '#present'
And match response.identifier[0].value == '12345'
And match response.name[0].family == 'Doe'
And match response.name[0].given == ['John', 'Michael']
And match response.gender == 'male'
# Store patient ID for subsequent tests
* def patientId = response.id
Scenario: Search patients by identifier
Given path 'Patient'
And param identifier = '12345'
When method GET
Then status 200
And match response.resourceType == 'Bundle'
And match response.total == 1
And match response.entry[0].resource.identifier[0].value == '12345'
|
Authentication and Security Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| Feature: API Authentication and Authorization
Background:
* url 'https://api.healthcare.com'
Scenario: Obtain access token
Given path 'oauth/token'
And form field grant_type = 'client_credentials'
And form field client_id = 'healthcare_app'
And form field client_secret = 'secret123'
And form field scope = 'patient:read patient:write'
When method POST
Then status 200
And match response.access_token == '#present'
And match response.token_type == 'Bearer'
* def accessToken = response.access_token
Scenario: Access protected resource with valid token
Given path 'patients/123'
And header Authorization = 'Bearer ' + accessToken
When method GET
Then status 200
And match response.id == '123'
Scenario: Access denied without token
Given path 'patients/123'
When method GET
Then status 401
And match response.error == 'unauthorized'
Scenario: Access denied with invalid token
Given path 'patients/123'
And header Authorization = 'Bearer invalid_token'
When method GET
Then status 401
Scenario: Scope validation - insufficient permissions
Given path 'admin/users'
And header Authorization = 'Bearer ' + accessToken
When method GET
Then status 403
And match response.error == 'insufficient_scope'
|
Data-Driven Testing
External Data Sources
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Feature: Data-driven patient testing
Background:
* url 'https://api.healthcare.com'
* header Authorization = 'Bearer ' + accessToken
* table patients
| name | gender | birthDate | mrn |
| John Doe | male | 1990-01-01 | MRN001 |
| Jane Doe | female | 1985-05-15 | MRN002 |
| Bob Smith | male | 1975-12-20 | MRN003 |
Scenario Outline: Create patients from data table
Given path 'patients'
And request
"""
{
"name": "<name>",
"gender": "<gender>",
"birthDate": "<birthDate>",
"mrn": "<mrn>"
}
"""
When method POST
Then status 201
And match response.name == '<name>'
And match response.mrn == '<mrn>'
Examples:
| patients |
|
CSV Data Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # Using external CSV file
Scenario Outline: Process patient data from CSV
* def patientData = read('classpath:testdata/patients.csv')
Given path 'patients'
And request
"""
{
"firstName": "<firstName>",
"lastName": "<lastName>",
"dateOfBirth": "<dateOfBirth>",
"ssn": "<ssn>"
}
"""
When method POST
Then status 201
And match response.id == '#present'
Examples:
| read('classpath:testdata/patients.csv') |
|
Mock Server for Testing
Creating Mock Services
1
2
3
4
5
6
7
8
9
10
11
12
| Feature: Mock server for testing
Background:
* def mockPort = karate.start('mock-server.feature').port
* url 'http://localhost:' + mockPort
Scenario: Test with mock external service
Given path 'external/verify'
And param patientId = '123'
When method GET
Then status 200
And match response == { verified: true, source: 'mock' }
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # mock-server.feature
Feature: Mock external service
Background:
* configure cors = true
Scenario: pathMatches('/external/verify')
* def response = { verified: true, source: 'mock' }
* def responseStatus = 200
Scenario: pathMatches('/external/patient/{id}')
* def patientId = pathParams.id
* def response =
"""
{
"id": "#(patientId)",
"name": "Mock Patient",
"status": "active"
}
"""
|
Gatling Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // KarateGatlingTest.java
package com.qahivelab.performance;
import com.intuit.karate.gatling.PreDef;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import static io.gatling.javaapi.core.CoreDsl.*;
public class PatientApiLoadTest extends Simulation {
ScenarioBuilder patients = scenario("Patient API Load Test")
.exec(
karateFeature("classpath:com/qahivelab/tests/patients.feature")
.name("Get Patients")
);
{
setUp(
patients.injectOpen(
rampUsers(50).during(30), // 50 users over 30 seconds
constantUsersPerSec(10).during(60) // 10 users/sec for 60 seconds
)
).protocols(
karateProtocol()
);
}
}
|
Advanced Karate Features
Custom Java Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // CustomUtils.java
package com.qahivelab.utils;
public class CustomUtils {
public static String generatePatientId() {
return "PAT" + System.currentTimeMillis();
}
public static boolean isValidSSN(String ssn) {
return ssn != null && ssn.matches("\\d{3}-\\d{2}-\\d{4}");
}
public static String encryptPHI(String data) {
// Simplified encryption for demo
return "ENCRYPTED:" + data;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Feature: Using custom Java functions
Background:
* def CustomUtils = Java.type('com.qahivelab.utils.CustomUtils')
Scenario: Generate and validate patient data
* def patientId = CustomUtils.generatePatientId()
* def ssn = '123-45-6789'
* def isValidSSN = CustomUtils.isValidSSN(ssn)
* assert isValidSSN == true
Given path 'patients'
And request
"""
{
"id": "#(patientId)",
"ssn": "#(CustomUtils.encryptPHI(ssn))"
}
"""
When method POST
Then status 201
|
Database Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| Feature: Database validation
Background:
* def dbConfig =
"""
{
driverClassName: 'org.postgresql.Driver',
jdbcUrl: 'jdbc:postgresql://localhost:5432/healthcare',
username: 'test_user',
password: 'test_pass'
}
"""
Scenario: Validate patient data in database
Given path 'patients'
And request { name: 'John Doe', email: 'john@example.com' }
When method POST
Then status 201
* def patientId = response.id
# Validate in database
* def query = 'SELECT * FROM patients WHERE id = ' + patientId
* def dbResult = karate.callSingle('classpath:db-utils.feature', { query: query, config: dbConfig })
* match dbResult.name == 'John Doe'
* match dbResult.email == 'john@example.com'
|
Error Handling and Debugging
Comprehensive Error Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Feature: Error handling scenarios
Scenario: Handle various error conditions
# Test 400 - Bad Request
Given path 'patients'
And request { name: '', email: 'invalid-email' }
When method POST
Then status 400
And match response.errors contains 'Name is required'
And match response.errors contains 'Invalid email format'
# Test 404 - Not Found
Given path 'patients/999999'
When method GET
Then status 404
And match response.message == 'Patient not found'
# Test 409 - Conflict
Given path 'patients'
And request { name: 'John Doe', email: 'existing@example.com' }
When method POST
Then status 409
And match response.error == 'Email already exists'
# Test 500 - Server Error (with retry logic)
* configure retry = { count: 3, interval: 1000 }
Given path 'patients/trigger-error'
When method GET
Then status 500
|
Debugging Features
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Scenario: Debug complex test scenario
# Print variables for debugging
* print 'Testing patient creation with ID:', patientId
Given path 'patients'
And request requestBody
When method POST
Then status 201
# Detailed response logging
* print 'Response:', response
* print 'Response headers:', responseHeaders
* print 'Response status:', responseStatus
# Conditional debugging
* if (response.id == null) karate.log('ERROR: Patient ID not generated')
|
Best Practices for Karate Testing
1. Organize Tests Effectively
1
2
3
4
5
6
7
8
9
10
11
12
13
| src/test/java/
├── com/qahivelab/
│ ├── tests/
│ │ ├── patients/
│ │ │ ├── create-patient.feature
│ │ │ ├── update-patient.feature
│ │ │ └── search-patients.feature
│ │ ├── appointments/
│ │ └── common/
│ │ ├── authentication.feature
│ │ └── common-validations.feature
│ └── utils/
│ └── TestRunner.java
|
2. Reusable Functions
1
2
3
4
5
6
7
8
9
10
11
12
| # common/auth-utils.feature
Feature: Authentication utilities
Scenario: Get access token
Given url 'https://auth.healthcare.com'
And path 'oauth/token'
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
|
3. Environment Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // karate-config.js
function fn() {
var env = karate.env; // get java system property 'karate.env'
karate.log('karate.env system property was:', env);
if (!env) {
env = 'dev'; // default environment
}
var config = {
env: env,
baseUrl: 'https://api-dev.healthcare.com'
};
if (env == 'staging') {
config.baseUrl = 'https://api-staging.healthcare.com';
} else if (env == 'prod') {
config.baseUrl = 'https://api.healthcare.com';
}
return config;
}
|
Conclusion
Karate framework provides a powerful, accessible approach to API testing that bridges the gap between technical and business teams. Its natural language syntax, combined with robust testing capabilities, makes it an excellent choice for healthcare applications where both functional correctness and regulatory compliance are critical.
The framework’s built-in features for authentication, data validation, mocking, and performance testing create a comprehensive testing solution that scales with your application’s complexity while maintaining readability and maintainability.
By leveraging Karate’s capabilities, teams can build robust API test suites that provide confidence in their healthcare applications while enabling rapid development cycles and continuous integration practices.
Start with simple scenarios and gradually incorporate advanced features as your team becomes comfortable with Karate’s capabilities. The framework’s gentle learning curve makes it accessible to all team members while providing the power needed for comprehensive API testing.