On Fixtures — From Schema to Snapshot

On Fixtures

In 2022, I conducted a research project exploring various testing strategies, including how to generate and manage test fixtures. The goal was simple: find the most efficient and maintainable way to handle test data, particularly in projects using GraphQL.

Fragment-Specific, Type-Safe Fixture Generators

Since we were working with GraphQL, I focused on building type-safe, flexible fixture generators. Tools like test-data-bot, graphql-anywhere, and GraphQL Codegen proved essential.

Imagine your schema includes several non-nullable fields. Now suppose you’re writing a test for a fragment of that schema. One that doesn’t require every field. Without a tailored approach, generating valid test data becomes unnecessarily rigid.

Here’s a simplified version of such a schema:

type AbilityScore {
  __typename: AbilityScore
  description: [String!]
  full_name: String!
  index: String!
  name: String!
  skills: [String!]!
}

type SpellDc {
  __typename: SpellDc
  desc: String!
  success: String!
  type: AbilityScore!
}

With GraphQL Codegen and test-data-bot, we can build strongly-typed, reusable builders that reflect the schema’s structure.

// schema-type-builders.ts
import { build } from '@jackfranklin/test-data-bot';
import { AbilityScore, SpellDc } from '../generated/graphql';

const abilityScoreBuilder = build<AbilityScore>({
  fields: {
    __typename: 'AbilityScore',
    desc: ['A', 'B', 'C'],
    full_name: 'Full name of the ability builder',
    index: 'index',
    name: 'Name',
    skills: [],
  },
});

const spellDcBuilder = build<SpellDc>({
  fields: {
    __typename: 'SpellDc',
    desc: 'Example description',
    success: 'Half',
    type: abilityScoreBuilder(),
  },
});

This approach is powerful, but it comes with a challenge: how do you generate a fixture for only a subset (fragment) of the full schema, especially when some fields are non-nullable?

The Solution: Filtering with GraphQL Fragments

The answer lies in combining GraphQL fragments with graphql-anywhere filter utility. You define your fragment, and filter helps trim your fixture down to only what the fragment needs.

// GraphQL Codegen generates `SpellDcFragmentDoc`
const SPELL_DC_FRAGMENT = gql`
  fragment SpellDc on SpellDc {
    desc
    type {
      full_name
    }
  }
`;

Now you can filter the full fixture:

import { filter } from 'graphql-anywhere';
import { spellDcBuilder } from './schema-type-builders';
import { SpellDcFragmentDoc } from '../generated/graphql';

const spellDc = spellDcBuilder();

const finalFixture = filter(SpellDcFragmentDoc, spellDc);

Which produces:

{
  "desc": "Example description",
  "type": {
    "full_name": "Full name of the ability builder"
  }
}

Why This Matters

This method offers several benefits:

  • Type safety: Your fixture data always conforms to the schema.
  • Flexibility: You can target only the data you need; no more bloated test payloads.
  • Maintainability: As your schema evolves, your test fixtures remain resilient and easy to update.
  • Reusability: Builders can be composed, customized, and reused across tests and fragments.

This strategy has helped keep test data lean, consistent, and scalable, especially in GraphQL-heavy codebases. If you’re dealing with complex schemas and brittle test fixtures, this approach might just save you time and frustration.

#code