Skip to content

GraphQL Custom Scalars

Configure Schemathesis to generate appropriate test data for custom scalar types in your GraphQL schema.

Built-in support

Schemathesis automatically handles these common custom scalars without any configuration:

  • Date, Time, DateTime - ISO formatted date/time strings
  • UUID - Valid UUID strings
  • IP, IPv4, IPv6 - Valid IP addresses
  • BigInt, Long - Large integers
# Your schema:
scalar Date
scalar UUID

type Query {
  getEvents(date: Date!, id: UUID!): [Event]
}

Schemathesis automatically generates valid queries:

{ getEvents(date: "2023-12-25", id: "550e8400-e29b-41d4-a716-446655440000") }

Apollo Federation

Every federated subgraph exposes _entities and _service. They belong to the federation gateway protocol rather than to your API, so Schemathesis leaves them out and tests the subgraph's own Query and Mutation fields without extra configuration.

To test _entities anyway, select it explicitly and register a strategy for the _Any scalar. _Any carries an entity representation - an object with __typename plus that entity's @key fields - so a generic object strategy makes the resolver fail on nearly every call:

uvx schemathesis run http://localhost:8000/graphql --include-name 'Query._entities'
import graphql
from hypothesis import strategies as st

import schemathesis
from schemathesis.graphql import nodes


def representation(key: str) -> graphql.ObjectValueNode:
    return nodes.Object(
        [
            graphql.ObjectFieldNode(name=graphql.NameNode(value="__typename"), value=nodes.String("Product")),
            graphql.ObjectFieldNode(name=graphql.NameNode(value="id"), value=nodes.String(key)),
        ]
    )


schemathesis.graphql.scalar("_Any", st.sampled_from(["1", "2"]).map(representation))

Adding custom scalars

For scalars not covered by built-in support, register custom strategies before loading your schema:

import schemathesis
from hypothesis import strategies as st
from schemathesis.graphql import nodes

# Configure custom scalars
schemathesis.graphql.scalar("Email", st.emails().map(nodes.String))
schemathesis.graphql.scalar("PositiveInt", st.integers(min_value=1).map(nodes.Int))

# Load schema and run tests
schema = schemathesis.graphql.from_url("http://localhost:8000/graphql")


@schema.parametrize()
def test_graphql_api(case):
    case.call_and_validate()

Hypothesis strategies reference

For all available data generation strategies, see the Hypothesis strategies documentation.

Common scalar examples

String-based scalars:

schemathesis.graphql.scalar("Email", st.emails().map(nodes.String))
schemathesis.graphql.scalar("URL", st.urls().map(nodes.String))
schemathesis.graphql.scalar("Phone", st.from_regex(r"\+1-\d{3}-\d{3}-\d{4}").map(nodes.String))

Numeric scalars:

schemathesis.graphql.scalar("Percentage", st.integers(0, 100).map(nodes.Int))
schemathesis.graphql.scalar("Price", st.decimals(min_value=0, max_value=1000, places=2).map(nodes.Float))

Constrained scalars:

# Restricted date range
from datetime import date

schemathesis.graphql.scalar(
    "RecentDate", st.dates(min_value=date(2020, 1, 1), max_value=date(2030, 12, 31)).map(str).map(nodes.String)
)

Available AST node types

Use these schemathesis.graphql.nodes factories to wrap your generated values:

  • String(value), Int(value), Float(value), Boolean(value), Enum(value), Null
  • List(values), Object(fields) - For complex types (see advanced section)

Advanced: JSON scalars

For JSON scalars that accept arbitrary objects, you need to convert Python dictionaries to GraphQL AST nodes:

import graphql
from hypothesis import strategies as st

import schemathesis
from schemathesis.graphql import nodes


def dict_to_object_fields(data: dict) -> list:
    """Convert a dictionary to a list of ObjectFieldNode instances."""
    fields = []
    for key, value in data.items():
        name_node = graphql.NameNode(value=key)
        value_node = python_value_to_ast_node(value)
        field_node = graphql.ObjectFieldNode(name=name_node, value=value_node)
        fields.append(field_node)
    return fields


def python_value_to_ast_node(value):
    """Convert a Python value to the appropriate GraphQL AST ValueNode."""
    if value is None:
        return graphql.NullValueNode()
    elif isinstance(value, bool):
        return graphql.BooleanValueNode(value=value)
    elif isinstance(value, int):
        return graphql.IntValueNode(value=str(value))
    elif isinstance(value, float):
        return graphql.FloatValueNode(value=str(value))
    elif isinstance(value, str):
        return graphql.StringValueNode(value=value)
    elif isinstance(value, list):
        ast_values = [python_value_to_ast_node(item) for item in value]
        return graphql.ListValueNode(values=tuple(ast_values))
    elif isinstance(value, dict):
        fields = dict_to_object_fields(value)
        return graphql.ObjectValueNode(fields=tuple(fields))
    raise ValueError("Unsupported value")


# Register JSON scalar
alphabet = st.characters(min_codepoint=ord("A"), max_codepoint=ord("Z"))
schemathesis.graphql.scalar(
    "JSON",
    st.dictionaries(
        keys=st.text(min_size=1, max_size=10, alphabet=alphabet),
        values=st.recursive(
            st.text(alphabet=alphabet)
            | st.integers()
            | st.floats(allow_nan=False, allow_infinity=False)
            | st.booleans()
            | st.none(),
            lambda strategy: (
                st.lists(strategy, max_size=3)
                | st.dictionaries(keys=st.text(min_size=1, max_size=10, alphabet=alphabet), values=strategy, max_size=3)
            ),
        ),
        min_size=1,
        max_size=5,
    ).map(lambda d: nodes.Object(dict_to_object_fields(d))),
)