Skip to content

Commit

Permalink
Add integration tests against a gqlgen server
Browse files Browse the repository at this point in the history
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: #21
Issue: #24

Test plan: make check
  • Loading branch information
benjaminjkraft committed Aug 20, 2021
1 parent 7003923 commit e4adf41
Show file tree
Hide file tree
Showing 15 changed files with 2,773 additions and 52 deletions.
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ linters-settings:
- unnamedResult
- ifElseChain
- sloppyReassign
- typeDefFirst

settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
Expand All @@ -80,7 +81,7 @@ issues:
# Test-only deps are not restricted.
- linters:
- depguard
path: _test\.go$|internal/testutil/
path: _test\.go$|internal/testutil/|internal/integration/

- linters:
- errcheck
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ Khan Academy is a non-profit organization with a mission to provide a free, worl

### Tests

To run tests and lint, `make check`. (GitHub Actions also runs them.) Most of the tests are snapshot-based; see `generate/generate_test.go`. If `GITHUB_TOKEN` is available in the environment, it also checks that the example returns the expected output when run against the real API. This is configured automatically in GitHub Actions, but you can also use a [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with no scopes.
To run tests and lint, `make check`. (GitHub Actions also runs them.)

Notes for contributors:
- Most of the tests are snapshot-based; see `generate/generate_test.go`. All new code-generation logic should be snapshot-tested. Some code additionally has standalone unit tests, when convenient.
- Integration tests run against a gqlgen server in `internal/integration/integration_test.go`, and should cover everything that snapshot tests can't, including the GraphQL client code and JSON marshaling.
- If `GITHUB_TOKEN` is available in the environment, it also checks that the example returns the expected output when run against the real API. This is configured automatically in GitHub Actions, but you can also use a [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with no scopes. There's no need for this to cover anything in particular; it's just to make sure the example in fact works.

### Design

Expand Down
4 changes: 2 additions & 2 deletions example/genqlient.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# These are the defaults, and are just included to be explicit.
package: example
schema: schema.graphql
queries:
operations:
- genqlient.graphql
generated: generated.go

# We map github's DateTime type to Go's time.Time (which conveniently already
# defines MarshalJSON and UnmarshalJSAON).
# defines MarshalJSON and UnmarshalJSON).
scalars:
DateTime: time.Time
44 changes: 5 additions & 39 deletions generate/example_test.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,16 @@
package generate
package generate_test

import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)

func getRepoRoot(t *testing.T) string {
_, thisFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("runtime.Caller non-ok")
}

return filepath.Dir(filepath.Dir(thisFile))
}
"github.com/Khan/genqlient/internal/integration"
)

func TestGenerateExample(t *testing.T) {
configFilename := filepath.Join(getRepoRoot(t), "example", "genqlient.yaml")
config, err := ReadAndValidateConfig(configFilename)
if err != nil {
t.Fatal(err)
}

generated, err := 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)
}
}
}
integration.RunGenerateTest(t, "example/genqlient.yaml")
}

func TestRunExample(t *testing.T) {
Expand All @@ -53,7 +19,7 @@ func TestRunExample(t *testing.T) {
}

cmd := exec.Command("go", "run", "./example/cmd/example", "benjaminjkraft")
cmd.Dir = getRepoRoot(t)
cmd.Dir = integration.RepoRoot(t)
out, err := cmd.CombinedOutput()
if err != nil {
t.Error(err)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module github.com/Khan/genqlient
go 1.13

require (
github.com/99designs/gqlgen v0.13.0
github.com/bradleyjkemp/cupaloy/v2 v2.6.0
// Should match golangci-lint version in .github/workflows/go.yml
github.com/golangci/golangci-lint v1.42.0
github.com/stretchr/testify v1.7.0
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/tools v0.1.5
gopkg.in/yaml.v2 v2.4.0
Expand Down
38 changes: 29 additions & 9 deletions go.sum

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions internal/integration/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/integration/genqlient.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schema: schema.graphql
operations:
- "*_test.go"
generated: generated.go
66 changes: 66 additions & 0 deletions internal/integration/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Package integration contains genqlient's integration tests, which run
// against a real server.
//
// 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
10 changes: 10 additions & 0 deletions internal/integration/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Query {
me: User
user(id: ID!): User
}

type User {
id: ID!
name: String!
luckyNumber: Int
}
10 changes: 10 additions & 0 deletions internal/integration/server/gqlgen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schema:
- ../schema.graphql

exec:
filename: gqlgen_exec.go
package: server

model:
filename: gqlgen_models.go
package: server
Loading

0 comments on commit e4adf41

Please sign in to comment.