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.