GqlCase
GqlCase is a comprehensive GraphQL testing library designed specifically for Absinthe projects. It provides powerful macros and utilities to easily test GraphQL queries and mutations with support for authentication, custom headers, and import resolution.
Key Features
- Easy GraphQL testing - Load GraphQL documents from files or strings with simple macros
- Authentication support - Seamless JWT bearer token integration for authenticated queries
- Flexible header management - Configure default headers with override capabilities
- Import resolution - Support for
#importstatements in GraphQL files - Security by design - Built-in validation and size limits for safe operation
Installation
Add gql_case to your list of dependencies in mix.exs:
def deps do
[
{:gql_case, "~> 0.5"}
]
endBasic Setup
To use GqlCase in your tests, first create a configuration module:
defmodule MyApp.GqlCase do
use GqlCase,
gql_path: "/graphql",
jwt_bearer_fn: &MyApp.Guardian.encode_and_sign/1,
default_headers: [
{"x-app-version", "1.0.0"},
{"x-client-type", "test"}
]
endThen use it in your test modules:
defmodule MyApp.UserQueryTest do
use ExUnit.Case
use MyApp.GqlCase
@endpoint MyApp.Endpoint
load_gql_file("queries/GetUser.gql")
test "fetches user data" do
result = query_gql(variables: %{id: "123"})
assert %{"data" => %{"user" => %{"id" => "123"}}} = result
end
endCore Configuration
The use GqlCase directive accepts several required and optional parameters:
Required Parameters
gql_path- The endpoint path for GraphQL requests (e.g.,"/graphql")jwt_bearer_fn- Function to generate JWT tokens for authentication (arity 1)
Optional Parameters
default_headers- List of default headers applied to all requests
defmodule MyApp.GqlCase do
use GqlCase,
gql_path: "/api/graphql",
jwt_bearer_fn: &MyApp.Auth.create_token/1,
default_headers: [
{"accept", "application/json"},
{"x-api-version", "v1"}
]
endLoading GraphQL Documents
GqlCase provides two ways to load GraphQL documents into your test modules:
Loading from Files
Use load_gql_file/1 to load GraphQL documents from external files:
defmodule MyApp.ProductTest do
use MyApp.GqlCase
# Load from relative path
load_gql_file("queries/GetProducts.gql")
test "gets products" do
result = query_gql()
assert %{"data" => %{"products" => products}} = result
end
endLoading from Strings
Use load_gql_string/1 for inline GraphQL queries:
defmodule MyApp.SimpleTest do
use MyApp.GqlCase
load_gql_string """
query GetHello {
hello
}
"""
test "says hello" do
result = query_gql()
assert %{"data" => %{"hello" => "Hello, World!"}} = result
end
endImport System
GqlCase supports GraphQL import statements for modular query organization:
# fragments/UserFields.gql
fragment UserFields on User {
id
name
email
}
# queries/GetUser.gql
#import "fragments/UserFields.gql"
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}Basic Query Execution
Execute loaded GraphQL documents using the query_gql/1 macro:
Simple Queries
result = query_gql()
assert %{"data" => %{"hello" => "Hello, World!"}} = resultQueries with Variables
result = query_gql(variables: %{id: "123", name: "John"})
assert %{"data" => %{"user" => %{"id" => "123"}}} = resultAuthentication Features
GqlCase seamlessly integrates with JWT-based authentication systems:
Authenticated Queries
Pass a current_user to automatically generate and include JWT tokens:
user = %{id: "123", email: "user@example.com"}
result = query_gql(current_user: user)JWT Bearer Function
The JWT bearer function should accept a user struct or map and return {:ok, token, claims}:
defmodule MyApp.Auth do
def create_token(user) do
Guardian.encode_and_sign(user, %{}, ttl: {1, :hour})
end
endHeader Management
GqlCase provides a flexible header management system with multiple levels of configuration:
Default Headers
Set default headers that apply to all requests in your configuration:
use GqlCase,
gql_path: "/graphql",
jwt_bearer_fn: &MyApp.Auth.create_token/1,
default_headers: [
{"x-app-version", "1.0.0"},
{"accept-language", "en-US"}
]Module-Specific Headers
Override or add headers for specific test modules:
defmodule MyApp.AdminTest do
use ExUnit.Case
use MyApp.GqlCase, headers: [
{"x-admin-role", "super"},
{"x-feature-flag", "admin-panel"}
]
load_gql_string "query { adminData }"
test "accesses admin data" do
result = query_gql()
# Request includes both default headers and module-specific headers
end
endRuntime Header Overrides
Add or override headers for individual queries:
result = query_gql(
variables: %{id: "123"},
headers: [
{"x-request-id", "abc-123"},
{"x-app-version", "2.0.0"} # Overrides default
]
)Header Priority System
Headers are merged with the following priority (highest to lowest):
- Runtime headers (passed to
query_gql/1) - Authorization header (generated from
current_user) - Module-specific headers (from
use MyApp.GqlCase, headers: [...]) - Default headers (from configuration)
- Built-in headers (
{"content-type", "application/json"})
Advanced Usage
Error Handling Patterns
GqlCase validates GraphQL documents and provides detailed error information:
# File not found
load_gql_file("nonexistent.gql") # Raises LoaderError
# Invalid GraphQL syntax
load_gql_string "query { invalid syntax }" # Raises ParseError
# Missing import
load_gql_file("query_with_missing_import.gql") # Raises ImportErrorImport Resolution
Imports are resolved relative to the importing file's directory:
project/
├── test/
│ └── queries/
│ ├── fragments/
│ │ └── UserFields.gql
│ └── GetUser.gql# In GetUser.gql:
#import "fragments/UserFields.gql"Unicode Support
GqlCase fully supports Unicode in GraphQL documents:
load_gql_string """
query GetGreeting($name: String!) {
greeting(name: $name)
}
"""
result = query_gql(variables: %{name: "José"})Security Constraints
Built-in security measures include:
- File size limits - Query strings limited to 10MB
- Null byte detection - Prevents null byte injection
- Path validation - Ensures safe file path resolution
API Reference
Macros
load_gql_file(file_path)
Loads a GraphQL document from a file path relative to the calling module.
- Arguments:
file_path(string) - Path to the GraphQL file - Raises:
LoaderErrorif file cannot be read,ParseErrorif GraphQL is invalid
load_gql_string(query_string)
Loads a GraphQL document from an inline string.
- Arguments:
query_string(string) - GraphQL query/mutation string - Raises:
ParseErrorif GraphQL is invalid
query_gql(opts \\ [])
Executes the loaded GraphQL document against the configured endpoint.
- Options:
variables- Map of GraphQL variables (default:%{})current_user- User map for JWT authentication (default:nil)headers- Additional request headers (default:[])
- Returns: Decoded JSON response from GraphQL endpoint
Error Types
GqlCase.SetupError
Raised when GqlCase is configured incorrectly:
:missing_path- Nogql_pathprovided:missing_jwt_bearer_fn- No JWT function provided:invalid_jwt_bearer_fn- JWT function has wrong arity:double_declaration- Multipleload_gql_*calls in same module
GqlCase.GqlLoader.LoaderError
Raised when files cannot be loaded:
-
Contains
pathandreasonfor debugging
GqlCase.GqlLoader.ImportError
Raised when imported files cannot be found:
-
Contains
pathandparentfile information
GqlCase.GqlLoader.ParseError
Raised when GraphQL documents are invalid:
-
Contains
path, error message, and line number
For more examples and advanced usage patterns, see the test suite in this repository.