Testing with the HTTP client
Black-box integration tests against grx handlers using net/http/httptest and the client package (GraphQL-over-HTTP).
Use this guide for tests that should match production behavior. Use direct executor tests for focused schema and execution cases, then use an HTTP test for routing, middleware, transports, and serialization.
The recommended way to test a grx HTTP surface “for real” is to stand up the same http.Handler you ship (grx.NewServer / server.New) behind httptest.Server and drive it with github.com/grx-gql/grx/client.
That path matches browsers and curl more closely than calling exec.Executor directly, so you validate routing, transports, middleware, Content-Type / Accept, and serialization end-to-end.
Canonical sample
server/query_test.go and server/server_test.go (execGraphQL, wrapServerInHarness) mirror the patterns below - including custom headers via WithRequestHeader.
Minimal harness
client.New expects the full URL to the GraphQL path (scheme + host + GraphQLPath). With httptest, concatenate the server base URL with *server.Server.GraphqlPath (defaults normalize to /graphql):
package graph_test
import (
"context"
"net/http/httptest"
"testing"
"example.com/hello-grx/graph"
"github.com/grx-gql/grx"
"github.com/grx-gql/grx/client"
)
func TestGraphQLPing(t *testing.T) {
srv, err := grx.NewServer(grx.WithSchema(graph.Schema()))
if err != nil {
t.Fatalf("new server: %v", err)
}
ts := httptest.NewServer(srv)
t.Cleanup(ts.Close)
c := client.New(ts.URL + srv.GraphqlPath)
resp, err := c.Exec(context.Background(), &client.Request{
Query: `{ __typename }`,
})
if err != nil {
t.Fatalf("exec: %v", err)
}
if len(resp.Errors) != 0 {
t.Fatalf("graphql errors: %+v", resp.Errors)
}
}The Response envelope is [core.Response](https://pkg.go.dev/github.com/grx-gql/grx/core#Response)** (Data, **Errors**, optional incremental payloads). **Exec** sets **error** only for **transport** problems (DNS, connection refused, malformed JSON) - **GraphQL failures** arrive with **resp.Errors` nonempty instead.
Variables and operation names
package graph_test
import (
"context"
"github.com/grx-gql/grx/client"
)
// c is built with client.New in your httptest harness.
func ExampleVariables(c *client.Client) error {
resp, err := c.Exec(context.Background(), &client.Request{
OperationName: "GetUser",
Query: `
query GetUser($id: String!) {
user(id: $id) { id name }
}`,
Variables: map[string]any{"id": "user_42"},
})
_ = resp
return err
}client.New expects the GraphQL endpoint URL (**scheme + host + GraphQLPath), as shown in Minimal harness above.
Authenticated requests
Reuse the same Bearer/cookie/session headers your routers expect via client.WithRequestHeader:
package graph_test
import (
"context"
"github.com/grx-gql/grx/client"
)
// c is built with client.New in your httptest harness; tok is your bearer string.
func exampleAuth(c *client.Client, tok string) error {
resp, err := c.Exec(context.Background(), &client.Request{Query: `{ me { id } }`},
client.WithRequestHeader("Authorization", "Bearer "+tok),
)
_ = resp
return err
}client.WithHTTPClient swaps in a traced or short-timeout http.Client for table tests.
Persisted queries (extensions)
client.Request.Extensions maps to the JSON extensions object - wire APQ payloads the same way a browser client would (persisted-queries guide).
When you need HTTP details
Exec decodes 200 bodies into core.Response but discards http.Response.
Use [PostGraphQL](https://pkg.go.dev/github.com/grx-gql/grx/client#Client.PostGraphQL) when you must assert StatusCode (for example malformed JSON envelopes, gateway-only behaviour):
package graph_test
import (
"context"
"encoding/json"
"io"
"net/http"
"testing"
"github.com/grx-gql/grx/client"
)
func ExamplePostGraphQL(t *testing.T, c *client.Client, ctx context.Context) {
payload, err := json.Marshal(client.Request{Query: "{ __typename }"})
if err != nil {
t.Fatal(err)
}
httpResp, err := c.PostGraphQL(ctx, payload)
if err != nil {
t.Fatal(err)
}
defer httpResp.Body.Close()
body, err := io.ReadAll(httpResp.Body)
if err != nil {
t.Fatal(err)
}
if httpResp.StatusCode != http.StatusOK {
t.Fatalf("status %d: %s", httpResp.StatusCode, body)
}
var gql client.Response // alias of core.Response
if err := json.Unmarshal(body, &gql); err != nil {
t.Fatal(err)
}
}Assertions on Data
resp.Data is any (typically map[string]any for objects). Narrow with type asserts or marshal round-trip:
package graph_test
import (
"testing"
"github.com/grx-gql/grx/client"
)
func ExampleAssertions(t *testing.T, resp *client.Response) {
data, ok := resp.Data.(map[string]any)
if !ok {
t.Fatalf("data type %T", resp.Data)
}
user, ok := data["user"].(map[string]any)
if !ok {
t.Fatalf("user field %T", data["user"])
}
_ = user
}Alternatively json.Marshal / json.Unmarshal into typed structs mirrors production JSON codecs.
What client is not
clientspeaks HTTPPOSTonly. WebSocket / SSE subscriptions needgraphql-ws(or equivalent) or in-processExecutor.Subscribetests - outside this package (Realtime subscriptions).- It does not replace
go testunit tests besideexec,schema, andresolverhelpers when you isolate parsing/validation behaviour without HTTP.
See also
- Queries and mutations - building the schema under test
- client on pkg.go.dev
Last updated on