Testing

Everything here is a client — the one interface queries run through: anything with execute(query, variables:) returning {"data" => ..., "errors" => ...} (see transports). Fakes, failures, and cassettes all slot in wherever a real transport would.

require "graph_weaver/rspec" from your spec helper (or graph_weaver/testing outside rspec — never in production) for a zero-setup fake backend. FakeClient fabricates schema-correct responses for whatever query arrives: real enum values, valid __typename members, iso8601 date scalars — every fake casts cleanly through your generated structs.

fake = GraphWeaver::Testing::FakeClient.new(schema:)

person = PersonQuery.execute!(fake, id: "1").person
person.name       # => "Eliza Kertzmann" (faker-matched on field name, when faker is loaded)
person.birthday   # => a real Date

Pin what matters, keyed by GraphQL names (schema vocabulary — keys survive query refactors); "Type.field" beats "field":

GraphWeaver::Testing::FakeClient.new(schema:, overrides: {
  "Person.name" => "Daniel",
  "email" => -> { "test@example.com" },
})

With rspec, the setup is two lines in spec/support/graph_weaver.rb — the require, plus an explicit opt-in to per-example fakes (deliberately not a default: silently swapping every example onto a fake would be surprising). The schema auto-locates from the committed dump at GraphWeaver.schema_path:

require "graph_weaver/rspec"   # seed follows --seed

GraphWeaver::Testing.configure do |config|
  config.auto_fake = true              # every example runs against a fresh fake
  # config.schema = MySchema           # optional: an in-process class instead of the dump
  # config.mode = :faker               # or :literal (plain typed values); nil = auto
  # config.overrides = { "Person.name" => "Daniel" }
  # config.list_size = 1..3
  # config.null_chance = 0.1           # nullable fields go nil sometimes
end

With the rspec integration, rspec --seed 1234 reproduces fake data along with test order, and auto_fake installs a seeded fake as the app client per example (generate modules without a baked client: so they consult GraphWeaver.client). mode: picks value fabrication: :faker (semantic, field-name matched — raises if the gem is missing), :literal (plain type-derived), or nil to auto-detect faker.

Test-only queries don't have to live in app/ — the conventional paths are appendable lists, so the same support file can register a spec-local set that load_generated! (and the Railtie) pick up:

GraphWeaver.generated_paths << "spec/support/graphql/generated"
GraphWeaver.queries_paths << "spec/support/graphql/queries"

Simulating failures — every failure mode is just a client, so error-handling paths are testable without a server that misbehaves on cue:

Failure = GraphWeaver::Testing::Failure

PersonQuery.execute(id: "1", client: Failure.transport)             # TransportError (cause preserved)
PersonQuery.execute(id: "1", client: Failure.server(status: 502))   # ServerError
PersonQuery.execute(id: "1", client: Failure.throttled)             # QueryError, code THROTTLED
PersonQuery.execute(id: "1", client: Failure.stale_schema)          # schema_stale? => true
PersonQuery.execute(id: "1", client: Failure.graphql("boom", data: {...}))  # partial failure

# retries: clients run in sequence (the last repeats) — here, two
# transport failures and then a FakeClient serving good responses
fake = GraphWeaver::Testing::FakeClient.new(schema:)
GraphWeaver::Testing::Sequence.new(Failure.transport, Failure.transport, fake)

# type mismatch: corrupt: derives a wrong-typed wire value for the field —
# casting raises GraphWeaver::TypeError (overrides remain the manual escape hatch)
GraphWeaver::Testing::FakeClient.new(schema:, corrupt: "Person.birthday")

# stale schema naming a real (sampled) field
Failure.stale_schema(schema: MySchema)

# field-level partial failure with real GraphQL null propagation: the error
# lands with its concrete path and nulls bubble to the nearest nullable spot
GraphWeaver::Testing::FakeClient.new(schema:, fail_at: { path: "person.email", code: "PRIVATE" })

Capture and replay — cassettes record real API responses and replay them offline, above the transport (no HTTP interception):

# records against the live client when the file is missing, replays after
cassette = GraphWeaver::Testing::Cassette.use("github", client: live)

Re-record with GRAPHWEAVER_RECORD=1, anonymize before committing (config.anonymize = true scrubs as recordings happen, or rake graph_weaver:cassettes:anonymize after) — the full workflow guide is cassettes.