Graphql-go: gqltesting not retrieving data from mongodb

Created on 25 Mar 2018  路  8Comments  路  Source: graph-gophers/graphql-go

I'm confused on how to get the data from mgo in gqltesting.RunTests.

I'm getting the data as expected in graphiQL, but gqltesting retrieves a FAIL. Could anyone please tell me what I'm doing wrong or how to debug this?

not found
--- FAIL: TestPost (0.00s)
--- FAIL: TestPost/post_query (0.00s)
testing.go:51: got: {"post":{"id":"","slug":"","title":""}}
testing.go:52: want: {"post":{"id":"","slug":"second-post","title":"Hello second post"}}

func TestPost(t *testing.T) {
    rootSchema := graphql.MustParseSchema(gqlSchema.GetRootSchema(), &gqlResolver.Resolver{})
    slug := "second-post"
    title := "Hello second post"
    t.Run("post query", func(t *testing.T) {
        gqltesting.RunTests(t, []*gqltesting.Test{
            {
                Schema: rootSchema,
                Query: `
                    {
                        post(slug:"` + slug + `") {
                            slug
                            title
                        }
                    }
                `,
                ExpectedResult: `
                    {
                        "post": {
                            "slug": "` + slug + `",
                            "title": "` + title + `"
                        }
                    }
                `,
            },
        })
    })
}
// [...]
func (r *Resolver) Post(args struct{ Slug string }) *postResolver {

    oneResult := &post{}

    session, collection := mongo.Get("post")
    defer session.Close()

    err := collection.Find(bson.M{"slug": args.Slug}).Select(bson.M{}).One(&oneResult)
    if err != nil {
        fmt.Println(err)
    }

    if s := oneResult; s != nil {
        return &postResolver{oneResult}
    }

    return nil
}
// [...]

Most helpful comment

@astenmies I do not see a problem with gqltesting. You have a problem with writing tests in general. It is very difficult to test anything either when you have global variables that are not explicitly passed to the function.
In your case, I would use the context to pass the variable "mongo".

func main() {
    session, _ := GetMongo()
    middleware := mongoContextMiddleware(session)
    http.Handle("/graphql", middleware(cors.Default().Handler(&relay.Handler{Schema: graphqlSchema})))

    ...
}

func mongoContextMiddleware(mongo *mgo.Session) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := r.Context()
            ctx = context.WithValue(ctx, "mongo", mongo)
            r = r.WithContext(ctx)

            next.ServeHTTP(w, r)
        })
    }
}

type Resolver struct{}

func (r *Resolver) Post(ctx context.Context, args struct{ Slug string }) *postResolver {
    mongo, ok := ctx.Value("mongo").(*mgo.Session) // <---
    if !ok {
        // Could not get "mongo" from the context.
    }

    // One result is a pointer to type post.
    oneResult := &post{}

    ...
}

test:

func TestPost(t *testing.T) {
    mongo := ... // <---
    ctx := context.WithValue(context.TODO(), "mongo", mongo) // <---
    rootSchema := graphql.MustParseSchema(gqlSchema.GetRootSchema(), &gqlResolver.Resolver{})
    slug := "second-post"
    title := "Hello second post"
    t.Run("post query", func(t *testing.T) {
        gqltesting.RunTests(t, []*gqltesting.Test{
            {
                Schema: rootSchema,
                Context: ctx, // <---
                Query: ...,
                ExpectedResult: ...,
            },
        })
    })
}

For myself, I write interfaces and pass them either through context (as above) or within a resolver:

func main() {
    session, _ := GetMongo()
    graphqlSchema = graphql.MustParseSchema(Schema, &Resolver{
        mongo: session, // <---
    })
}

type Resolver struct{
    mongo *mgo.Session // <---
}

func (r *Resolver) Post(args struct{ Slug string }) *postResolver {
    session, collection := r.mongo.Get("post") // <---
    ...
}

test:

func TestPost(t *testing.T) {
    mongo := ...
    rootSchema := graphql.MustParseSchema(gqlSchema.GetRootSchema(), &gqlResolver.Resolver{
        mongo: mongo  // <---
    })
    slug := "second-post"
    title := "Hello second post"
    t.Run("post query", func(t *testing.T) {
        gqltesting.RunTests(t, []*gqltesting.Test{
            {
                Schema: rootSchema,
                Query: ...,
                ExpectedResult: ...,
            },
        })
    })
}

But it is better to separate the database and resolvers(code from my project):

func (r *Resolver) Login(ctx context.Context, args struct {
    Email    string
    Password string
}) (*UserTokenResolver, error) {
    return ResolveLoginUser(ctx, args.Email, args.Password)
}

func ResolveLoginUser(ctx context.Context, email, password string) (*UserTokenResolver, error) {
    stor, ok := storage.FromContext(ctx)
    if !ok || stor.User == nil {
        logrus.Panic("[ResolveLoginUser] Storage not initialed")
        return nil, nil
    }

    trimEmail := emailx.Normalize(email)
    trimPassword := strings.TrimSpace(password)

    emailErr := emailx.ValidateFast(trimEmail)
    if emailErr != nil || trimPassword == "" {
        return nil, errNotValidLogin
    }

    // get user from storage
    user, err := stor.User.GetByEmail(trimEmail)
    if err != nil {
        logrus.Error(err)
        return nil, errNotValidLogin
    }

    if user == nil || !user.ValidatePassword(password) {
        return nil, errNotValidLogin
    }

    return ResolveUserToken(ctx, &UserResolver{User: user})
}

All 8 comments

I have not used mongo connector in golang before.
But I am curious is there any db mocking tools for mongodb or is the connector connected with your db when running your test

Hmm I didn't realize people were using gqltesting externally.

Is this common? Have you found the package helpful?

If so, perhaps this could be a separate repository in the organization?

@OscarYuen I made a gist here and my project "lychee" is kind of an extended version of that gist. I'm quite new in Go and not sure how to achieve the connection you are talking about for running tests.

@tonyghita I found gqltesting helpful especially when testing with some hard coded mock data. Just having issues to understand how to test with real mongodb data. Some projects I stumbled upon are also using it externally. I'm not experienced enough to advise to separate the repo or not, but I definitely like the package.

Btw thanks to both of you as your projects graphql-go-example and go-graphql-starter are actually my main inspiration for learning cool new things and creating lychee.

@astenmies I do not see a problem with gqltesting. You have a problem with writing tests in general. It is very difficult to test anything either when you have global variables that are not explicitly passed to the function.
In your case, I would use the context to pass the variable "mongo".

func main() {
    session, _ := GetMongo()
    middleware := mongoContextMiddleware(session)
    http.Handle("/graphql", middleware(cors.Default().Handler(&relay.Handler{Schema: graphqlSchema})))

    ...
}

func mongoContextMiddleware(mongo *mgo.Session) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := r.Context()
            ctx = context.WithValue(ctx, "mongo", mongo)
            r = r.WithContext(ctx)

            next.ServeHTTP(w, r)
        })
    }
}

type Resolver struct{}

func (r *Resolver) Post(ctx context.Context, args struct{ Slug string }) *postResolver {
    mongo, ok := ctx.Value("mongo").(*mgo.Session) // <---
    if !ok {
        // Could not get "mongo" from the context.
    }

    // One result is a pointer to type post.
    oneResult := &post{}

    ...
}

test:

func TestPost(t *testing.T) {
    mongo := ... // <---
    ctx := context.WithValue(context.TODO(), "mongo", mongo) // <---
    rootSchema := graphql.MustParseSchema(gqlSchema.GetRootSchema(), &gqlResolver.Resolver{})
    slug := "second-post"
    title := "Hello second post"
    t.Run("post query", func(t *testing.T) {
        gqltesting.RunTests(t, []*gqltesting.Test{
            {
                Schema: rootSchema,
                Context: ctx, // <---
                Query: ...,
                ExpectedResult: ...,
            },
        })
    })
}

For myself, I write interfaces and pass them either through context (as above) or within a resolver:

func main() {
    session, _ := GetMongo()
    graphqlSchema = graphql.MustParseSchema(Schema, &Resolver{
        mongo: session, // <---
    })
}

type Resolver struct{
    mongo *mgo.Session // <---
}

func (r *Resolver) Post(args struct{ Slug string }) *postResolver {
    session, collection := r.mongo.Get("post") // <---
    ...
}

test:

func TestPost(t *testing.T) {
    mongo := ...
    rootSchema := graphql.MustParseSchema(gqlSchema.GetRootSchema(), &gqlResolver.Resolver{
        mongo: mongo  // <---
    })
    slug := "second-post"
    title := "Hello second post"
    t.Run("post query", func(t *testing.T) {
        gqltesting.RunTests(t, []*gqltesting.Test{
            {
                Schema: rootSchema,
                Query: ...,
                ExpectedResult: ...,
            },
        })
    })
}

But it is better to separate the database and resolvers(code from my project):

func (r *Resolver) Login(ctx context.Context, args struct {
    Email    string
    Password string
}) (*UserTokenResolver, error) {
    return ResolveLoginUser(ctx, args.Email, args.Password)
}

func ResolveLoginUser(ctx context.Context, email, password string) (*UserTokenResolver, error) {
    stor, ok := storage.FromContext(ctx)
    if !ok || stor.User == nil {
        logrus.Panic("[ResolveLoginUser] Storage not initialed")
        return nil, nil
    }

    trimEmail := emailx.Normalize(email)
    trimPassword := strings.TrimSpace(password)

    emailErr := emailx.ValidateFast(trimEmail)
    if emailErr != nil || trimPassword == "" {
        return nil, errNotValidLogin
    }

    // get user from storage
    user, err := stor.User.GetByEmail(trimEmail)
    if err != nil {
        logrus.Error(err)
        return nil, errNotValidLogin
    }

    if user == nil || !user.ValidatePassword(password) {
        return nil, errNotValidLogin
    }

    return ResolveUserToken(ctx, &UserResolver{User: user})
}

Thanks a lot @GhostRussia, this is very helpful

I accidentally realized that the test I initially posted in this issue is actually working when the containing file is placed alongside main.go (top level of the app). Thus without context...

Does anyone pls have more insight on why?

You've probably got some global state leaking between tests in the same package. Is there another test in the top level?

I don't think this issue is specific to this library, so I'll close it for now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tonyghita picture tonyghita  路  3Comments

jakubdal picture jakubdal  路  6Comments

bsr203 picture bsr203  路  6Comments

rogchap picture rogchap  路  5Comments

AndreasBackx picture AndreasBackx  路  4Comments