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

Installation

Add gql_case to your list of dependencies in mix.exs:

def deps do
  [
    {:gql_case, "~> 0.5"}
  ]
end

Basic 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"}
    ]
end

Then 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
end

Core Configuration

The use GqlCase directive accepts several required and optional parameters:

Required Parameters

Optional Parameters

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

Loading 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
end

Loading 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
end

Import 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!"}} = result

Queries with Variables

result = query_gql(variables: %{id: "123", name: "John"})
assert %{"data" => %{"user" => %{"id" => "123"}}} = result

Authentication 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
end

Header 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
end

Runtime 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):

  1. Runtime headers (passed to query_gql/1)
  2. Authorization header (generated from current_user)
  3. Module-specific headers (from use MyApp.GqlCase, headers: [...])
  4. Default headers (from configuration)
  5. 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 ImportError

Import 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:

API Reference

Macros

load_gql_file(file_path)

Loads a GraphQL document from a file path relative to the calling module.

load_gql_string(query_string)

Loads a GraphQL document from an inline string.

query_gql(opts \\ [])

Executes the loaded GraphQL document against the configured endpoint.

Error Types

GqlCase.SetupError

Raised when GqlCase is configured incorrectly:

GqlCase.GqlLoader.LoaderError

Raised when files cannot be loaded:

GqlCase.GqlLoader.ImportError

Raised when imported files cannot be found:

GqlCase.GqlLoader.ParseError

Raised when GraphQL documents are invalid:


For more examples and advanced usage patterns, see the test suite in this repository.