4.9 KiB
4.9 KiB
Testing GraphQL in Go
gqlgen — Client Harness
The github.com/99designs/gqlgen/client package drives the full stack (directives, middleware, resolvers) via an http.Handler:
func TestCreateUser(t *testing.T) {
// Build the full handler with real dependencies (use a test DB)
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{
Resolvers: &graph.Resolver{
DB: testDB,
},
}))
c := client.New(srv)
var resp struct {
CreateUser struct {
User struct {
ID string
Email string
}
Errors []struct{ Message string }
}
}
c.MustPost(`
mutation CreateUser($email: String!, $name: String!) {
createUser(input: {email: $email, name: $name}) {
user { id email }
errors { message }
}
}
`, &resp,
client.Var("email", "alice@example.com"),
client.Var("name", "Alice"),
client.AddHeader("Authorization", "Bearer test-token"),
)
require.Empty(t, resp.CreateUser.Errors)
require.Equal(t, "alice@example.com", resp.CreateUser.User.Email)
}
For unit testing individual resolvers, call resolver methods directly with a constructed Resolver and a real context.Context — no HTTP overhead.
gqlgen — Testing with DataLoaders
Wrap the test server with the DataLoader middleware so resolver tests exercise the full batching path:
srv := handler.NewDefaultServer(es)
h := dataloaders.Middleware(testDB, srv)
c := client.New(h)
gqlgen — Testing Subscriptions
Use client.Subscription to test subscription resolvers:
sub := c.Subscription(`subscription { messageAdded(room: "general") { content } }`)
defer sub.Close()
// Trigger an event
publishMessage("general", "hello")
var event struct{ MessageAdded struct{ Content string } }
err := sub.Next(&event)
require.NoError(t, err)
require.Equal(t, "hello", event.MessageAdded.Content)
graph-gophers — gqltesting
func TestUser(t *testing.T) {
gqltesting.RunTests(t, []*gqltesting.Test{
{
Schema: schema,
Query: `{ user(id: "1") { name email } }`,
ExpectedResult: `{"user":{"name":"Alice","email":"alice@example.com"}}`,
},
{
Schema: schema,
Query: `{ user(id: "999") { name } }`,
ExpectedErrors: []*gqlerrors.QueryError{
{Message: "user not found", Extensions: map[string]any{"code": "NOT_FOUND"}},
},
},
})
}
For HTTP-level tests:
func TestRelayHandler(t *testing.T) {
body := `{"query":"{ user(id: \"1\") { name } }"}`
req := httptest.NewRequest(http.MethodPost, "/graphql", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
relay.Handler{Schema: schema}.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"Alice"`)
}
Testing Error Handling
Verify error extensions reach the client:
var resp struct {
Errors []struct {
Message string
Extensions struct{ Code string }
}
}
c.Post(`{ user(id: "999") { name } }`, &resp)
require.Equal(t, "NOT_FOUND", resp.Errors[0].Extensions.Code)
Testing Auth Directives (gqlgen)
Test the directive function directly:
func TestHasRoleDirective(t *testing.T) {
ctx := context.WithValue(context.Background(), userKey, &domain.User{Role: "USER"})
_, err := HasRole(ctx, nil, func(ctx context.Context) (any, error) {
return "ok", nil
}, model.RoleAdmin)
require.Error(t, err)
var gqlErr *gqlerror.Error
require.True(t, errors.As(err, &gqlErr))
require.Equal(t, "FORBIDDEN", gqlErr.Extensions["code"])
}
Table-Driven Tests
func TestUserQueries(t *testing.T) {
tests := []struct {
name string
query string
vars map[string]any
wantCode string
wantName string
}{
{"existing user", `query($id:ID!){user(id:$id){name}}`, map[string]any{"id": "1"}, "", "Alice"},
{"missing user", `query($id:ID!){user(id:$id){name}}`, map[string]any{"id": "999"}, "NOT_FOUND", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var resp struct {
User *struct{ Name string }
Errors []struct {
Extensions struct{ Code string }
}
}
c.Post(tt.query, &resp, client.Var("id", tt.vars["id"]))
if tt.wantCode != "" {
require.Equal(t, tt.wantCode, resp.Errors[0].Extensions.Code)
} else {
require.Equal(t, tt.wantName, resp.User.Name)
}
})
}
}
For testing patterns across the codebase, see the samber/cc-skills-golang@golang-testing skill.