Add integration tests against a gqlgen server (#50)
## Summary: We have lots of tests covering codegen, but not a lot that actually run the code. For things where all we do is generate types, that's (mostly) fine (especially now that we actually build the code), but as we generate more nontrivial non-type code we need to actually run it. So I wrote some integration tests that spin up a little gqlgen server, and make calls to it; we can add more over time especially as the JSON marshalling logic gets complex (to support fragments). They're more work to write than the snapshot tests, but of course they can test a lot more. In addition to gqlgen, I pulled in testify assert/require, because I really wanted to be able to use assert.Equal and such for these. I didn't bother converting existing tests, although I assume they will become useful elsewhere in time. Both gqlgen and testify are of course only used in tests. Fixes #21 and #24. Issue: https://github.com/Khan/genqlient/issues/21 Issue: https://github.com/Khan/genqlient/issues/24 ## Test plan: make check Author: benjaminjkraft Reviewers: aberkan, dnerdy, benjaminjkraft, csilvers, MiguelCastillo Required Reviewers: Approved by: aberkan, dnerdy Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Test (1.13), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Test (1.13), ✅ Lint Pull request URL: https://github.com/Khan/genqlient/pull/50
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package integration
|
||||
|
||||
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
)
|
||||
|
||||
// queryWithVariablesResponse is returned by queryWithVariables on success.
|
||||
type queryWithVariablesResponse struct {
|
||||
User queryWithVariablesUser `json:"user"`
|
||||
}
|
||||
|
||||
// queryWithVariablesUser includes the requested fields of the GraphQL type User.
|
||||
type queryWithVariablesUser struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LuckyNumber int `json:"luckyNumber"`
|
||||
}
|
||||
|
||||
// simpleQueryMeUser includes the requested fields of the GraphQL type User.
|
||||
type simpleQueryMeUser struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LuckyNumber int `json:"luckyNumber"`
|
||||
}
|
||||
|
||||
// simpleQueryResponse is returned by simpleQuery on success.
|
||||
type simpleQueryResponse struct {
|
||||
Me simpleQueryMeUser `json:"me"`
|
||||
}
|
||||
|
||||
func simpleQuery(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
) (*simpleQueryResponse, error) {
|
||||
var retval simpleQueryResponse
|
||||
err := client.MakeRequest(
|
||||
ctx,
|
||||
"simpleQuery",
|
||||
`
|
||||
query simpleQuery {
|
||||
me {
|
||||
id
|
||||
name
|
||||
luckyNumber
|
||||
}
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
nil,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
func queryWithVariables(
|
||||
ctx context.Context,
|
||||
client graphql.Client,
|
||||
id string,
|
||||
) (*queryWithVariablesResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"id": id,
|
||||
}
|
||||
|
||||
var retval queryWithVariablesResponse
|
||||
err := client.MakeRequest(
|
||||
ctx,
|
||||
"queryWithVariables",
|
||||
`
|
||||
query queryWithVariables ($id: ID!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
name
|
||||
luckyNumber
|
||||
}
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
schema: schema.graphql
|
||||
operations:
|
||||
- "*_test.go"
|
||||
generated: generated.go
|
||||
@@ -0,0 +1,66 @@
|
||||
// Package integration contains genqlient's integration tests, which run
|
||||
// against a real server (defined in internal/integration/server/server.go).
|
||||
//
|
||||
// These are especially important for cases where we generate nontrivial logic,
|
||||
// such as JSON-unmarshaling.
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/Khan/genqlient/internal/integration/server"
|
||||
)
|
||||
|
||||
func TestSimpleQuery(t *testing.T) {
|
||||
_ = `# @genqlient
|
||||
query simpleQuery { me { id name luckyNumber } }`
|
||||
|
||||
ctx := context.Background()
|
||||
server := server.RunServer()
|
||||
defer server.Close()
|
||||
client := graphql.NewClient(server.URL, http.DefaultClient)
|
||||
|
||||
resp, err := simpleQuery(ctx, client)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "1", resp.Me.Id)
|
||||
assert.Equal(t, "Yours Truly", resp.Me.Name)
|
||||
assert.Equal(t, 17, resp.Me.LuckyNumber)
|
||||
}
|
||||
|
||||
func TestVariables(t *testing.T) {
|
||||
_ = `# @genqlient
|
||||
query queryWithVariables($id: ID!) { user(id: $id) { id name luckyNumber } }`
|
||||
|
||||
ctx := context.Background()
|
||||
server := server.RunServer()
|
||||
defer server.Close()
|
||||
client := graphql.NewClient(server.URL, http.DefaultClient)
|
||||
|
||||
resp, err := queryWithVariables(ctx, client, "2")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "2", resp.User.Id)
|
||||
assert.Equal(t, "Raven", resp.User.Name)
|
||||
assert.Equal(t, -1, resp.User.LuckyNumber)
|
||||
|
||||
resp, err = queryWithVariables(ctx, client, "374892379482379")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Zero(t, resp.User)
|
||||
}
|
||||
|
||||
func TestGeneratedCode(t *testing.T) {
|
||||
// TODO(benkraft): Check that gqlgen is up to date too. In practice that's
|
||||
// less likely to be a problem, since it should only change if you update
|
||||
// the schema, likely too add something new, in which case you'll notice.
|
||||
RunGenerateTest(t, "internal/integration/genqlient.yaml")
|
||||
}
|
||||
|
||||
//go:generate go run github.com/Khan/genqlient genqlient.yaml
|
||||
@@ -0,0 +1,10 @@
|
||||
type Query {
|
||||
me: User
|
||||
user(id: ID!): User
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
luckyNumber: Int
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
schema:
|
||||
- ../schema.graphql
|
||||
|
||||
exec:
|
||||
filename: gqlgen_exec.go
|
||||
package: server
|
||||
|
||||
model:
|
||||
filename: gqlgen_models.go
|
||||
package: server
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package server
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LuckyNumber *int `json:"luckyNumber"`
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||
)
|
||||
|
||||
func intptr(v int) *int { return &v }
|
||||
|
||||
var users = []*User{
|
||||
{ID: "1", Name: "Yours Truly", LuckyNumber: intptr(17)},
|
||||
{ID: "2", Name: "Raven", LuckyNumber: intptr(-1)},
|
||||
}
|
||||
|
||||
func userByID(id string) *User {
|
||||
for _, user := range users {
|
||||
if id == user.ID {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Me(ctx context.Context) (*User, error) {
|
||||
return userByID("1"), nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {
|
||||
return userByID(id), nil
|
||||
}
|
||||
|
||||
func RunServer() *httptest.Server {
|
||||
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
|
||||
gqlgenServer.AddTransport(transport.POST{})
|
||||
return httptest.NewServer(gqlgenServer)
|
||||
}
|
||||
|
||||
type (
|
||||
resolver struct{}
|
||||
queryResolver struct{}
|
||||
)
|
||||
|
||||
func (r *resolver) Query() QueryResolver { return &queryResolver{} }
|
||||
|
||||
//go:generate go run github.com/99designs/gqlgen
|
||||
@@ -0,0 +1,57 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/Khan/genqlient/generate"
|
||||
)
|
||||
|
||||
// RepoRoot returns the root of the genqlient repository,
|
||||
func RepoRoot(t *testing.T) string {
|
||||
_, thisFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller non-ok")
|
||||
}
|
||||
|
||||
root := filepath.Dir(filepath.Dir(filepath.Dir(thisFile)))
|
||||
if _, err := os.Stat(filepath.Join(root, ".gitignore")); err != nil {
|
||||
t.Fatal(fmt.Errorf("doesn't look like repo root: %v", err))
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// RunGenerateTest checks that running genqlient with the given
|
||||
// repo-root-relative config file would not produce any changes to the
|
||||
// checked-in files.
|
||||
func RunGenerateTest(t *testing.T, relConfigFilename string) {
|
||||
configFilename := filepath.Join(RepoRoot(t), relConfigFilename)
|
||||
config, err := generate.ReadAndValidateConfig(configFilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
generated, err := generate.Generate(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for filename, content := range generated {
|
||||
expectedContent, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(content, expectedContent) {
|
||||
t.Errorf("mismatch in %s", filename)
|
||||
if testing.Verbose() {
|
||||
t.Errorf("got:\n%s\nwant:\n%s\n", content, expectedContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user