Skip to main content

HTTP REQUESTS

GraphQL Testing

Test GraphQL APIs seamlessly with Karate's built-in support for queries, mutations, variables, and powerful JsonPath validation for deeply nested responses.

Why GraphQL with Karate?

  • Native text handling: Use the text keyword to prevent JSON parsing issues with GraphQL syntax
  • Dynamic queries: Build queries with placeholders, variables, and conditional fragments
  • Powerful validation: JsonPath and fuzzy matching handle complex, nested GraphQL responses effortlessly
  • Data-driven testing: Combine with Scenario Outline for parameterized query testing

Basic GraphQL Query

Send a simple GraphQL query using the text keyword:

Feature: Basic GraphQL query

Scenario: Query a single hero
Given url 'https://api.example.com'
And path 'graphql'
And text query = """{ hero { name height mass } }"""
And request { query: '#(query)' }
And header Accept = 'application/json'
When method post
Then status 200
And match response.data.hero.name == '#string'

Use text instead of def to prevent Karate from parsing GraphQL syntax as JSON.

Text Keyword

GraphQL queries look like JSON but are not valid JSON. The text keyword tells Karate to treat the content as raw text, avoiding parsing errors. Always use text for GraphQL queries defined inline.

GraphQL Mutations

Execute mutations to create or update data:

Feature: GraphQL mutations

Scenario: Create a new user
Given url apiUrl
And path 'graphql'
And text mutation =
"""
mutation {
createUser(name: "Alice Johnson", email: "alice@example.com") {
id
name
email
createdAt
}
}
"""
And request { query: '#(mutation)' }
When method post
Then status 200
And match response.data.createUser ==
{
id: '#number',
name: 'Alice Johnson',
email: 'alice@example.com',
createdAt: '#string'
}

Query Variables

Pass dynamic variables to GraphQL queries for reusability:

Feature: GraphQL with variables

Scenario: Query hero by ID
Given url apiUrl
And path 'graphql'
And text query =
"""
query GetHero($heroId: Int!) {
hero(id: $heroId) {
name
height
mass
homeworld
}
}
"""
And def variables = { heroId: 1001 }
And request { query: '#(query)', variables: '#(variables)' }
When method post
Then status 200
And match response.data.hero.name == 'Luke Skywalker'

Scenario: Mutation with variables
Given path 'graphql'
And text mutation =
"""
mutation UpdateUser($id: Int!, $email: String!) {
updateUser(id: $id, email: $email) {
id
email
updatedAt
}
}
"""
And def variables = { id: 123, email: 'newemail@example.com' }
And request { query: '#(mutation)', variables: '#(variables)' }
When method post
Then status 200
And match response.data.updateUser.email == 'newemail@example.com'

File-Based Queries

Organize reusable queries in .graphql or .gql files:

Feature: File-based GraphQL queries

Scenario: Load query from file
# File: get-hero.graphql
# query GetHero($id: Int!) {
# hero(id: $id) { name height mass }
# }

Given url apiUrl
And path 'graphql'
And def query = read('get-hero.graphql')
And def variables = { id: 1001 }
And request { query: '#(query)', variables: '#(variables)' }
When method post
Then status 200
And match response.data.hero == { name: '#string', height: '#number', mass: '#number' }

Files with .graphql and .gql extensions are automatically treated as text strings.

File Organization

Organize GraphQL queries in a dedicated folder like src/test/java/queries/ alongside your feature files. Use descriptive names like get-user-by-id.graphql and create-order-mutation.graphql for easy maintenance.

Placeholder Substitution

Use placeholders with Scenario Outline for data-driven GraphQL testing:

Feature: Data-driven GraphQL queries

Scenario Outline: Query heroes by name
Given url apiUrl
And path 'graphql'
# Placeholders use <name> syntax
And text query =
"""
{
hero(name: "<heroName>") {
name
height
mass
}
}
"""
And request { query: '#(query)' }
When method post
Then status 200
And match response.data.hero.name == '<heroName>'

Examples:
| heroName |
| Luke Skywalker |
| Darth Vader |
| Leia Organa |

Dynamic Query Building

Build queries dynamically with the replace keyword:

Feature: Dynamic query construction

Scenario: Conditionally include fields
Given url apiUrl
And path 'graphql'
And text queryTemplate =
"""
{
hero(id: <heroId>) {
name
<optionalFields>
}
}
"""
# Include different fields based on condition
And def includeDetails = true
And def fields = includeDetails ? 'height
mass
homeworld' : ''
And replace queryTemplate
| token | value |
| heroId | 1001 |
| optionalFields | fields |
And request { query: '#(queryTemplate)' }
When method post
Then status 200
And match response.data.hero.name == '#present'

Response Validation

Validate complex GraphQL responses with JsonPath and fuzzy matching:

Feature: GraphQL response validation

Scenario: Validate nested response structure
Given url apiUrl
And path 'graphql'
And text query =
"""
{
allUsers {
users {
id
name
posts {
title
comments {
author
text
}
}
}
}
}
"""
And request { query: '#(query)' }
When method post
Then status 200

# Validate array sizes
And match response.data.allUsers.users == '#[3]'

# JsonPath for deep queries
And def firstUser = response.data.allUsers.users[0]
And match firstUser.name == '#string'
And match firstUser.posts == '#[_ > 0]'

# Fuzzy matching for dynamic data
And match each response.data.allUsers.users ==
{
id: '#number',
name: '#string',
posts: '#array'
}

# Validate nested comments
And def allComments = karate.jsonPath(response, '$.data.allUsers.users[*].posts[*].comments[*]')
And match allComments == '#[_ > 0]'
And match each allComments == { author: '#string', text: '#string' }

GraphQL Best Practices

Query organization:

  • Store reusable queries in .graphql files for complex operations
  • Use inline text queries for simple, one-off tests
  • Group related queries in folders by feature or domain

Variable usage:

  • Always use variables for dynamic data (IDs, search terms, filters)
  • Define variables in Background for scenario reuse
  • Use fuzzy matching '#string' and '#number' for dynamic response fields

Error handling:

  • GraphQL returns 200 even with errors; check response.errors array
  • Validate both success and error response structures
  • Use match for schema validation on error objects

When to use text vs def:

  • Use text for inline GraphQL queries (prevents parsing issues)
  • Use def when reading .graphql files (already treated as text)
  • Use def for variables objects (standard JSON)

Next Steps