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"
    }
    """

Performance and Load Testing

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.