Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apollo-server-testing context is not receiving the req object #2277

Closed
iensu opened this issue Feb 6, 2019 · 39 comments
Closed

apollo-server-testing context is not receiving the req object #2277

iensu opened this issue Feb 6, 2019 · 39 comments

Comments

@iensu
Copy link

iensu commented Feb 6, 2019

I am trying to write tests for header handling. I'm setting up the tests as follows:

it('should handle Accept-Language headers', async () => {
    const acceptLanguage = 'sv, en';

    const request = nock(config.API_URL)
      .matchHeader('accept-language', acceptLanguage)
      .get('/articles')
      .reply(200, []);

    const server = createServer();

    const { query } = createTestClient(server);

    await query({
      query: '{ articles { id } }',
      http: {
        headers: { 'accept-language': acceptLanguage },
      },
    });

    expect(request.isDone()).to.eql(true);
  });

The test relies on the req object being passed to the context function in the server setup, but I'm only getting an empty object. Is this the expected behavior? If so, how do you go about writing tests for authentication headers etc?

Thanks!

@rohan-deshpande
Copy link

rohan-deshpande commented Feb 10, 2019

Experiencing this and it's making it impossible to write proper integration tests for logged in users etc.,

Edit: well, from the examples it seems like you are supposed to basically return a fixture from the server instance context, but I don't think this is ideal.

I want to be able to test how my app integrates with apollo server, how can this be done properly if the behaviour of the apollo server when writing tests is different to what it will be like in production?

vitorbal added a commit to vitorbal/apollo-server that referenced this issue Mar 21, 2019
…ation

Fixes apollographql#2277.

`apollo-server-testing` relies on `apollo-server-core` to execute queries
for integration tests.

However, `apollo-server-core` was not passing the `req` object to the
`context` callback function used to create the server.

This is fine if you're using a mock `context` object directly in
your tests, but for tests that want to run a `context` callback that
contains logic that depends on that `req` object, it would fail.

Note that long-term the best fix for this is that `apollo-server-testing`
should use the `ApolloServer` class from `apollo-server-express` internally,
since it seems like that is the class that is used by default in `apollo-server`.
vitorbal added a commit to vitorbal/apollo-server that referenced this issue Mar 21, 2019
…ation

Fixes apollographql#2277.

`apollo-server-testing` relies on `apollo-server-core` to execute queries
for integration tests, via the `executeOperation` method.

However, when executing a query via `executeOperation`, `apollo-server-core`
was not passing the `req` object to the `context` callback function used
to create the server.

This is fine if you're using a mock `context` object directly in
your tests, but for tests that want to run a `context` callback that
contains logic that depends on that `req` object, it would fail.

Note that long-term the best fix for this is that `apollo-server-testing`
should use the `ApolloServer` class from `apollo-server-express` internally,
instead of `ApolloServerBase` from `apollo-server-core`, since it seems
like that is the class that is used by default in `apollo-server`.
vitorbal added a commit to vitorbal/apollo-server that referenced this issue Mar 21, 2019
…ation

Fixes apollographql#2277.

`apollo-server-testing` relies on `apollo-server-core` to execute queries
for integration tests, via the `executeOperation` method.

However, when executing a query via `executeOperation`, `apollo-server-core`
was not passing the `req` object to the `context` callback function used
to create the server.

This is fine if you're using a mock `context` object directly in
your tests, but for tests that want to run a `context` callback that
contains logic that depends on that `req` object, it would fail.

Note that long-term the best fix for this is that `apollo-server-testing`
should use the `ApolloServer` class from `apollo-server-express` internally,
instead of `ApolloServerBase` from `apollo-server-core`, since it seems
like that is the class that is used by default in `apollo-server`.
@bugeats
Copy link

bugeats commented May 15, 2019

I just ran into this.

I don't consider it a true integration test unless you can actually test how your server's true context is constructed. To hide behind a mock context doesn't actually test anything.

@lnmedrano
Copy link

lnmedrano commented Jul 23, 2019

I've been facing a similar issue.

I've implemented a token-based authentication.
The server checks that a valid token is sent in a header and then it passes the token in the context to the resolvers.
When i'm making tests there is no sense in setting a test-token and mocking the token-validating function because that is what I want to test.
It would be very useful to have tests that ensure that this part of the code is working correctly, doing something like this:

const { query: _query, mutate } = createTestClient(
  new ApolloServer({
    schema,
    context: ({ req }) => {
      const token = req.headers.token || '';
      return validateToken(token).then(validated => ({ tokenValidated: validated, token }));
    }
  })
);

@gcoda
Copy link

gcoda commented Jul 23, 2019

i am using workaround based on .createHandler - here is a gist

could not figure out why server.executeOperation({http}) does not work

@the-vampiire
Copy link

the-vampiire commented Jul 27, 2019

here are two solutions i came up with:

if you are comfortable "polluting" the prototype
the prototype will only be modified when using this testServer. will not impact your actual server.

const { ApolloServer } = require("apollo-server-X"); // import from your flavor
const serverConfig = require("../api/config"); 

// server config is the object you pass into the ApolloServer constructor
// { resolvers, typeDefs, schemaDirectives, context, ... }

// execute the context function to get the base context object
// optionally you can add a default req or res in this step
const baseContext = serverConfig.context({});

// use 1 or more of the following functions as needed

ApolloServer.prototype.setContext = function setContext(newContext) {
  this.context = newContext;
}

ApolloServer.prototype.mergeContext = function mergeContext(partialContext) {
  this.context = Object.assign({}, this.context, partialContext);
}

ApolloServer.prototype.resetContext = function resetContext() {
  this.context = baseContext;
}

module.exports = {
  testServer: new ApolloServer({
    ...serverConfig,
    context: baseContext,
  }),
  baseContext, // easy access in tests
}

"cleaner" solution with a subclass

const { ApolloServer } = require("apollo-server-X"); // import from your flavor
const serverConfig = require("../api/config"); 

// server config is the object you pass into the ApolloServer constructor
// { resolvers, typeDefs, schemaDirectives, context, ... }

// execute the context function to get the base context object
// optionally you can add a default req or res in this step
const baseContext = serverConfig.context({});

// create a test server subclass with the methods built in
class ApolloTestServer extends ApolloServer {
  constructor(config) {
    super(config);
    this.context = baseContext;
  }

  setContext(newContext) {
    this.context = newContext;
  }

  mergeContext(partialContext) {
    this.context = Object.assign({}, this.context, partialContext);
  }

  resetContext() {
    this.context = baseContext;
  }
}

module.exports = {
  baseContext,
  testServer: new ApolloTestServer(serverConfig),
};

usage for either approach

const { createTestClient } = require("apollo-server-testing");
const { testServer, baseContext } = require("./test-utils/test-server");

const { query, mutate } = createTestClient(testServer);

test("something", async () => {
  // set / reset / merge the context as needed before calling query or mutate
  testServer.mergeContext({
    req: { headers: { Authorization: `Bearer ${token}` } },
  });

  const res = await query({ query, variables });
  expect(res)...
});

@liyikun
Copy link

liyikun commented Aug 6, 2019

here are two solutions i came up with:

if you are comfortable "polluting" the prototype
the prototype will only be modified when using this testServer. will not impact your actual server.

const { ApolloServer } = require("apollo-server-X"); // import from your flavor
const serverConfig = require("../api/config"); 

// server config is the object you pass into the ApolloServer constructor
// { resolvers, typeDefs, schemaDirectives, context, ... }

// execute the context function to get the base context object
// optionally you can add a default req or res in this step
const baseContext = serverConfig.context({});

// use 1 or more of the following functions as needed

ApolloServer.prototype.setContext = function setContext(newContext) {
  this.context = newContext;
}

ApolloServer.prototype.mergeContext = function mergeContext(partialContext) {
  this.context = Object.assign({}, this.context, partialContext);
}

ApolloServer.prototype.resetContext = function resetContext() {
  this.context = baseContext;
}

module.exports = {
  testServer: new ApolloServer({
    ...serverConfig,
    context: baseContext,
  }),
  baseContext, // easy access in tests
}

"cleaner" solution with a subclass

const { ApolloServer } = require("apollo-server-X"); // import from your flavor
const serverConfig = require("../api/config"); 

// server config is the object you pass into the ApolloServer constructor
// { resolvers, typeDefs, schemaDirectives, context, ... }

// execute the context function to get the base context object
// optionally you can add a default req or res in this step
const baseContext = serverConfig.context({});

// create a test server subclass with the methods built in
class ApolloTestServer extends ApolloServer {
  constructor(config) {
    super(config);
    this.context = baseContext;
  }

  setContext(newContext) {
    this.context = newContext;
  }

  mergeContext(partialContext) {
    this.context = Object.assign({}, this.context, partialContext);
  }

  resetContext() {
    this.context = baseContext;
  }
}

module.exports = {
  baseContext,
  testServer: new ApolloTestServer(serverConfig),
};

usage for either approach

const { createTestClient } = require("apollo-server-testing");
const { testServer, baseContext } = require("./test-utils/test-server");

const { query, mutate } = createTestClient(testServer);

test("something", async () => {
  // set / reset / merge the context as needed before calling query or mutate
  testServer.mergeContext({
    req: { headers: { Authorization: `Bearer ${token}` } },
  });

  const res = await query({ query, variables });
  expect(res)...
});

in typescript, context is private 🤕

@the-vampiire
Copy link

@liyikun 😢 well that sucks lol

Sent with GitHawk

@vitorbal
Copy link

vitorbal commented Aug 9, 2019

I tried some of the solutions listed here and none of them worked for me. I ended up creating a new package that mimics the apollo-server-testing API, but allows for passing in a mock req object (or sets one for you automatically, with sensible defaults):

https://github.com/zapier/apollo-server-integration-testing

We've been using it successfully at my company for the last 6 months to write real integration tests. Posting it here in case anyone else is interested in giving it a try :)

@abernix abernix added 🧪 testing ⛲️ feature New addition or enhancement to existing solutions 🧞‍♂️ enhancement and removed ⛲️ feature New addition or enhancement to existing solutions labels Aug 26, 2019
@abernix abernix added this to the Release 3.x milestone Aug 26, 2019
@cappslock
Copy link

I think you could put a spy around the context factory and inject a request object that way. I've been tinkering a bit with it and I'll post a gist if I get it to work.

@cappslock
Copy link

cappslock commented Sep 25, 2019

Here is the gist of the approach I mentioned yesterday. This is a module that creates and exports a singleton test client. This assumes you're using Jest, and that your context factory is in its own module. I think the paths are self explanatory but feel free to ask if anything isn't clear.

You could probably modify this to be a test client builder rather than a singleton which would be a little safer so you don't have to worry about maintaining token state between tests.

let token;

/* IMPORTANT
 * This uses `doMock` instead of `mock` to prevent hoisting and allow the use of
 * a local variable (specifically, `token`). This `doMock` call MUST be before
 * the require() call to ../graphql/graphqlServer, as otherwise that module will
 * not use the mocked context factory.
 */
jest.doMock('../graphql/context.js', () => {
  const contextFactory = jest.requireActual('../graphql/context.js');
  return jest.fn(() =>
    contextFactory({
      req: { headers: token ? { authorization: `Bearer ${token}` } : {} }
    })
  );
});

const { createTestClient } = require('apollo-server-testing');
const createServer = require('../graphql/graphqlServer');

// see https://www.apollographql.com/docs/apollo-server/testing/testing/
const testGraphqlClient = createTestClient(createServer());

testGraphqlClient.setToken = function(newToken) {
  token = newToken;
};

module.exports = testGraphqlClient;

In case the context factory isn't clear, the idea is that your ApolloServer is instantiated like so:

new ApolloServer({
  ...otherStuff
  context: require('./context.js')
})

... and the context.js file is like:

module.exports = async ({ req }) => {
  return {
    ...allYourFancyContextStuff
  };
};

Hope this helps someone.

@peterbabic
Copy link

peterbabic commented Oct 17, 2019

Apologies for recommending other package, but it is from the same ecosystem, not the competing one. I hope it is not displeasing the authors.

Since I could not find any pleasant solution, I have ended up writing tests using apollo-boost:

import ApolloClient, { gql } from "apollo-boost"
import fetch from "node-fetch"

test("graphql response with auth header", async () => {
    const uri = "http://localhost:4000/graphql"
    const client = new ApolloClient({
        uri,
        fetch,
        request: operation => {
            operation.setContext({
                headers: {
                    authorization: "Bearer <token>",
                },
            })
        },
    })

    const queryResponse = await client.query({ query: gql`query{ ... } ` })
    const mutationResponse = await client.mutate({ mutation: gql`mutation{ ... }` })

    expect(queryResponse.data).toBe("expected-qeury-data")
    expect(mutationResponse.data).toBe("expected-mutation-data")
})

I am not really sure if this is still called "integration testing" and not "e2e" but it works really for me. Thought this might come handy to someone still struggling.

@bluwy
Copy link

bluwy commented Dec 29, 2019

I found a much simpler solution to set the initial context arguments. What it does is that it wraps the context function with another function that "injects" the context argument:

const { ApolloServer } = require('apollo-server') // Or `apollo-server-express`
const { createTestClient } = require('apollo-server-testing')

/**
 * Simple test client with custom context argument
 * @param config Apollo Server config object
 * @param ctxArg Argument object to be passed
 */
const testClient = (config, ctxArg) => {
  return createTestClient(new ApolloServer({
    ...config,
    context: () => config.context(ctxArg)
  }))
}

Usage:

const { query, mutate } = testClient(config, { req: { headers: { authorization: '<token>' } } })

// Use as usual
query({
  query: GET_USER,
  variables: { id: 1 }
})

If you need to set custom context arguments per query or mutate, it can be further extended to be something like this:

const { ApolloServer } = require('apollo-server') // Or `apollo-server-express`
const { createTestClient } = require('apollo-server-testing')

/**
 * Test client with custom context argument that can be set per query or mutate call
 * @param config Apollo Server config object
 * @param ctxArg Default argument object to be passed
 */
const testClient = (config, ctxArg) => {
  const baseCtxArg = ctxArg
  let currentCtxArg = baseCtxArg

  const { query, mutate, ...others } = createTestClient(new ApolloServer({
    ...config,
    context: () => config.context(currentCtxArg)
  }))

  // Wraps query and mutate function to set context arguments
  const wrap = fn => ({ ctxArg, ...args }) => {
    currentCtxArg = ctxArg != null ? ctxArg : baseCtxArg
    return fn(args)
  }

  return { query: wrap(query), mutate: wrap(mutate), ...others }
}

Usage:

const { query, mutate } = testClient(config, { req: { headers: { authorization: '<token>' } } })

// Set context argument per query or mutate
query({
  query: GET_USER,
  variables: { id: 1 },
  ctxArg: { req: { headers: { authorization: '<new-token>' } } }
})

Hope this helps :)

@Aaronik
Copy link

Aaronik commented Feb 14, 2020

I know there are many solutions here already, but for some reason they weren't sitting well with me. I made a very thin wrapper around createTestClient that does perfectly for me. Maybe it'll help someone else, too :)

import { createTestClient } from 'apollo-server-testing'
import { ApolloServer } from 'apollo-server'

// This is a simple wrapper around apollo-server-testing's createTestClient.
// A massive shortcoming of that code is that the ctx object gets passed in
// to the context function as an empty object. So, critically, we can't test
// authorization headers, which is gonna be like all the requests we do in our
// tests. This wrapper allows some headers to be passed in. See:
// https://github.com/apollographql/apollo-server/issues/2277
export default function (server: ApolloServer, headers = {} as any) {
  // @ts-ignore B/c context is marked as private.
  const oldContext = server.context

  const context = ({ req, res }) => {
    return oldContext({ res, req: { ...req, headers }})
  }

  const serverWithHeaderContext = Object.assign({}, server, { context })
  // @ts-ignore -- Typescript doesn't know about __proto__, huh...
  serverWithHeaderContext.__proto__ = server.__proto__

  return createTestClient(serverWithHeaderContext)
}

And instead of import { createTestClient } from 'apollo-server-testing', just do a import createTestClient from 'path/to/this/module'. Enjoy!

@vikei
Copy link

vikei commented Mar 12, 2020

I found a solution through supertest

// create-app.ts
import { ApolloServer } from 'apollo-server-express'
import { config as configEnv } from 'dotenv'
import express from 'express'
import 'reflect-metadata'
import { createSchema } from './create-shema'
import { getContext } from './get-context'

configEnv()

export async function createApp() {
  const server = new ApolloServer({
    schema: await createSchema(),
    context: getContext,
  })

  const app = express()

  server.applyMiddleware({ app })

  return { server, app }
}
// query test
  test('get auth user', async () => {
    const { app } = await createApp()
    const [userData] = fakeUsers

    const user = await usersService.findUser({ email: userData.email })
    const token = authService.createToken(user!.id)

    const meQuery = `
      {
        me {
          id
          name
          email
          passwordHash
        }
      }
    `

    const result = await makeQuery({ app, query: meQuery, token })

    expect(result.errors).toBeUndefined()
    expect(result.data).toBeDefined()

    expect(result.data).toHaveProperty('me', {
      id: user!.id.toString(),
      name: user!.name,
      email: user!.email,
      passwordHash: expect.any(String),
    })
  })
// make-query
import { Express } from 'express'
import supertest from 'supertest'

type MakeQuery = {
  app: Express
  query: string
  token?: string
  variables?: object
}

export async function makeQuery({ token, query, app, variables }: MakeQuery) {
  const headers: { Authorization?: string } = {}

  if (token) {
    headers.Authorization = `Bearer ${token}`
  }

  const { body } = await supertest(app)
    .post('/graphql')
    .send({ query, variables })
    .set(headers)

  return body
}

@KristianWEB
Copy link

I found a much simpler solution to set the initial context arguments. What it does is that it wraps the context function with another function that "injects" the context argument:

const { ApolloServer } = require('apollo-server') // Or `apollo-server-express`
const { createTestClient } = require('apollo-server-testing')

/**
 * Simple test client with custom context argument
 * @param config Apollo Server config object
 * @param ctxArg Argument object to be passed
 */
const testClient = (config, ctxArg) => {
  return createTestClient(new ApolloServer({
    ...config,
    context: () => config.context(ctxArg)
  }))
}

Usage:

const { query, mutate } = testClient(config, { req: { headers: { authorization: '<token>' } } })

// Use as usual
query({
  query: GET_USER,
  variables: { id: 1 }
})

If you need to set custom context arguments per query or mutate, it can be further extended to be something like this:

const { ApolloServer } = require('apollo-server') // Or `apollo-server-express`
const { createTestClient } = require('apollo-server-testing')

/**
 * Test client with custom context argument that can be set per query or mutate call
 * @param config Apollo Server config object
 * @param ctxArg Default argument object to be passed
 */
const testClient = (config, ctxArg) => {
  const baseCtxArg = ctxArg
  let currentCtxArg = baseCtxArg

  const { query, mutate, ...others } = createTestClient(new ApolloServer({
    ...config,
    context: () => config.context(currentCtxArg)
  }))

  // Wraps query and mutate function to set context arguments
  const wrap = fn => ({ ctxArg, ...args }) => {
    currentCtxArg = ctxArg != null ? ctxArg : baseCtxArg
    return fn(args)
  }

  return { query: wrap(query), mutate: wrap(mutate), ...others }
}

Usage:

const { query, mutate } = testClient(config, { req: { headers: { authorization: '<token>' } } })

// Set context argument per query or mutate
query({
  query: GET_USER,
  variables: { id: 1 },
  ctxArg: { req: { headers: { authorization: '<new-token>' } } }
})

Hope this helps :)

I've tried to implement your example but I'm getting the error: Cannot read property 'headers' of undefined
Check the code

@bluwy
Copy link

bluwy commented Mar 17, 2020

@KristianWEB You're instantiating a different server than the one in src/index.js. You should use the latter instead since its context property passes the req value.

Also, you shouldn't be passing an ApolloServer instance to testClient. The first parameter actually accepts the apollo server config object, but since the instance had the context property too, it also worked.

@KristianWEB
Copy link

KristianWEB commented Mar 17, 2020

@BjornLuG What should the apollo server config object be in this case? I couldn't find any information about it.

Edit: I see what you meant: adding the resolvers and the schema. Now the apollo server doesnt recognize the context ( config.context is not a function )

    const { mutate, query } = testClient({
      resolvers,
      typeDefs,
    });

When I try to add the context to the testClient config in this way:

    const { mutate, query } = testClient({
      resolvers,
      typeDefs,
      context: () => ({}),
    });

It throws me the headers of undefined error again. I've tried this as well:

    const { mutate, query } = testClient({
      resolvers,
      typeDefs,
      context: async ({ req }) => ({ req }),
    });

But with no success I'm still receiving Cannot destructure property 'req' of 'undefined' as it is undefined error.

How do I instantiate the testClient properly? Thanks again!
Code

@the-vampiire
Copy link

have a look at the solution i posted originally. it works #2277 (comment)

@serv
Copy link

serv commented Apr 6, 2020

Unfortunately, apollo-server-testing doesn't work when you need to work with req and res objects in the server side. Also none of the solutions really worked out for me because req and res are still mocks created manually by developers. As a result, when you do something like res.cookie(...) in your express server side, you'll have an error because the mock res object doesn't have something like cookie function.

I had to resort to using a graphql client created from apollo-boost and making real graphql requests to do integration testing.

@jdeflaux
Copy link

jdeflaux commented Apr 9, 2020

Found a solution to still test production code, but mock the req

// createApolloServer.js

// authorizationStore is anything that could add fields to req 

export const createContext = (authorizationStore) => ({ req }) => {
  authorizationStore.setProfileId(req); // of course in tests this won't work because req is null
  return {
    userId: req.profile.id
  };
};

export const createApolloServer (authorizationStore, context = createContext) => {
  const apolloServer = new ApolloServer({
    schema,
    context: context(authorizationStore)
  });
  return apolloServer;
};

in your main production code:

import { createApolloServer } from './createApolloServer';

// const authenticationStore = up to you
const apolloServer = createApolloServer(authenticationStore)

and in your test:

import { createApolloServer, createContext } from './createApolloServer';

it('should work ', async () => {
    const authenticationStore = {
      set: jest.fn()
    };
    const mockCreateContext = () => {
      const req = {
        profile: {
          id: 123
        }
      };
      return createContext(authenticationStore)({ req });
    };

    const apolloServer = createApolloServer(authenticationStore, mockCreateContext);

    const { query } = createTestClient(apolloServer);

    // etc.
  });

@FrancescoSaverioZuppichini

Hi, is there a proper solution atm? It is crazy that there is no supported way to pass custom headers to the req.

@KristianWEB
Copy link

@FrancescoSaverioZuppichini I've implemented a custom testClient. Check it out

@FrancescoSaverioZuppichini

Nice, thank you @KristianWEB

@mattvb91
Copy link

mattvb91 commented Jun 5, 2020

@KristianWEB this has also helped us with our ApolloServer testing. Thank you!

@momelnyk
Copy link

momelnyk commented Jul 1, 2020

The following code worked for me.

import { ApolloServer } from "apollo-server"
import { createTestClient } from "apollo-server-testing"

const createTestServer = () => {
  const server = new ApolloServer({ 
    // ...
  });
  return server;
};

test('auth', async () => {
  const server = createTestServer();
  const { mutate } = createTestClient(server);

  server.context = () => {
    const req = {
      headers: {
        "authorization": `accessToken ${ accessToken }`
      }
    }
    return { req }
  };

  response = await mutate({
    mutation: MUTATION_1,
    variables: { var1, var2 }
  });
}

@abury
Copy link

abury commented Jul 7, 2020

So, for those like me who came here looking for how to do this who use TS and don't use express, I ended up writing my own stub for the context function. It's really not ideal and I'm still beyond shocked that something so basic isn't handled, but you gotta work with what you've got, so here we are:

// wherever_you_are_creating_your_apollo_server.ts
export interface Context {
    currentUser?: IUser;
}

export async function createApolloServer(
    findUserContext?: (req: any) => Promise<Context>
) {
    const schema = await buildSchema({  ...  });

    const contextToUse = findUserContext || validateUser;

    const apolloServer = new ApolloServer({
        schema,
        ...
        context: async ({ req }) => {
            return await contextToUse(req);
        },
    });
    return apolloServer;
}

export const validateUser = async (req: any) => {
        return findUserWithRequest(req);
};

export const findUserWithRequest = async (req: any): Promise<Context> => {
        // Find your user here
        const headers = req?.headers["cookie/auth/token/whatever_youre_using"]
        const currentUser = User.findFromHeaders(headers)
        return { currentUser }
}
// my_handy_spec_helper.ts

import { createApolloServer, findUserWithRequest } from '~/server

export async function setupAuthTestApolloClient(token: string) {
    const apolloServer = await createApolloServer(findUserForToken(token));
    const { query, mutate } = createTestClient(apolloServer);
    return {
        query,
        mutate,
    };
}

const findUserForToken = (token: String) => async (req: any) => {
    const headers = req?.headers || [];
    // construct your stubbed headers here
    headers['token'] = token

    const stubbedReq = {
        ...req,
        headers: headers,
    };

    return findUserWithRequest(stubbedReq);
};
// my_important_spec.spec.ts

import {
    setupTestDBConnection,
    setupTestApolloClient,
} from "../../utils/helpers";

describe("User queries", () => {
    it("can fetch the currently logged in user", async () => {
        const connection = await setupTestDBConnection();

        const email = "a_random_email@gmail.com.au.net";
        const password = await hashPassword("battery_horse_staple");
        await createUser({ email, password });

        const token = generateJwtToken({ email });

        const { query } = await setupAuthTestApolloClient(token);
        try {
            const res = await query({
                query: GET_ME,
            });

            const loggedInUser: IUser = res?.data?.me;

            expect(loggedInUser.email).toEqual(email);
        } finally {
            connection.close();
        }
    });
});

With this setup you can

@citypaul
Copy link

citypaul commented Jul 21, 2020

Are there any plans to solve this as part of the official testing package?

As others have already pointed out, not being able to mock out the req parameter on the context means that you cannot currently use apollo-server-testing to do true integration testing.

@nvme0
Copy link

nvme0 commented Aug 26, 2020

Similar to @Aaronik's solution (#2277 (comment)), this works fine for my use-case. I'm using type-graphql and have a custom auth guard that handles jwt verification.

createApolloTestServer.ts

import { ApolloServer } from "apollo-server-micro";
import { createSchema } from "pages/api/graphql";

export const createApolloTestServer = async (headers = {} as any) => {
  const schema = await createSchema();
  const server = new ApolloServer({
    schema,
    debug: true,
    context: ({ req, res }) => {
      return {
        res,
        req: {
          ...req,
          headers
        }
      };
    }
  });
  return server;
};

testSomthing.spec.ts

import { createTestClient } from "apollo-server-testing";
import { createApolloTestServer } from "test-utils/createApolloTestServer";

beforeAll(async () => {
   apolloServer = await createApolloTestServer({
      authorization: `Bearer ${access_token}`
   });
});

it("does something", async () => {
  const { mutate } = createTestClient(apolloServer);
  // req: { headers: { authorization: "..." } } will be accessible in the resolver
  const response = await mutate({
    mutation: GQL_MUTATION,
    variables: {
      ...mutationVariables
    }
  });
});

@adamwdennis
Copy link

We are still experiencing this issue.

1 similar comment
@erwzqsdsdsf
Copy link

We are still experiencing this issue.

@preetjdp
Copy link

Amalgamated the work by @vitorbal, the Zapier Team Apollo-Server-Integration-Testing, and the Apollo Server Testing Library

Here's some of the changes that have been made

  1. Allows to pass headers while making an operation, this is in contrast to how headers are set in Apollo-Server-Integration-Testing
  2. Changed the operation response type to GraphQLResponse to give more type information while testing

https://gist.github.com/preetjdp/178643c5854ae775b005834be6687edc

@dipiash
Copy link

dipiash commented Nov 11, 2020

Amalgamated the work by @vitorbal, the Zapier Team Apollo-Server-Integration-Testing, and the Apollo Server Testing Library

Here's some of the changes that have been made

  1. Allows to pass headers while making an operation, this is in contrast to how headers are set in Apollo-Server-Integration-Testing
  2. Changed the operation response type to GraphQLResponse to give more type information while testing

https://gist.github.com/preetjdp/178643c5854ae775b005834be6687edc

Hello, @preetjdp!

Last few days I try to update dependencies for apollo-server. I have some problems with integration tests. And I found your solution and try.

I think that line 142 is incorrect
https://gist.github.com/preetjdp/178643c5854ae775b005834be6687edc#file-createtestclient-ts-L142

...args.variables,

If I start test with query:

const res = await query({
   query: SOME_QUERY,
   variables: { ids: [-3, -2] },
   headers: {
      authorization: undefined,
   },
})

I have the incorrect error:

HttpQueryError: {"errors":[{"message":"Variable \"$ids\" of required type \"[Int]!\" was not provided.","locations":[{"line":1,"column":18}],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}]}

But if I change line 142 on (I'm forked your gist https://gist.github.com/dipiash/bf69150518baf8ddb9b0e136fdf3c9d0#file-createtestclient-ts-L142):

variables: args.variables,

I have correct result.

@preetjdp
Copy link

Thanks for the heads up @dipiash, I faced this last week myself,
I've updated the gist as well

@roschaefer
Copy link
Contributor

Hey friends, maybe I don't see the problem, but what stops you from creating a
pipeline to your actual context function?

import { JWT_SECRET } from './config.js'
import jwt from 'jsonwebtoken'

// export your context function from a file and import it in your tests
export default function ({ req }) {
  let token = req.headers.authorization || ''
  token = token.replace('Bearer ', '')
  try {
    return jwt.verify(token, JWT_SECRET)
  } catch (e) {
    return {}
  }
}

You can construct a fake express.Request object and pipe it to your context
function. I call this fake object reqMock.

Of course it would be nice to import a constructor from express module in
order to create a real instance of express.Request.

import { createTestClient } from 'apollo-server-testing'
import { GraphQLError } from 'graphql'
import schema from '../schema'
import { ApolloServer, gql } from 'apollo-server'
import context from '../context' // this is your context

let reqMock
let resMock
const contextPipeline = () => context({ req: reqMock, res: resMock })
const server = new ApolloServer({ schema, context: contextPipeline })

beforeEach(() => {
  // For me a mock `express.Request` containing some headers is enough.
  // I assume typescript will complain here:
  reqMock = { headers: {} }

  resMock = {}
})

describe('mutations', () => {
  describe('write', () => {
    const opts = {
      mutation: gql`
        mutation($postInput: PostInput!) {
          write(post: $postInput) {
            id
            title
            author {
              id
            }
          }
        }
      `,
      variables: { postInput: { title: 'New post' } }
    }

    describe('unauthenticated', () => {
      beforeEach(() => {
        reqMock = { headers: {} }
      })

      it('throws authorization error', async () => {
        await expect(mutate(opts)).resolves.toMatchObject({
          data: { write: null },
          errors: [
            new GraphQLError('Not Authorised!')
          ]
        })
      })
    })

    describe('authenticated', () => {
      beforeEach(() => {
        reqMock = {
          headers: {
            authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFsaWNlIiwiaWF0IjoxNjA2OTQyMzc4fQ.RSXUAq7IVrxb3WeIMJ3pBmszjJzmFBP97h-pINbi3sc'
          }
        }
      })

      it('returns created post', async () => {
        await expect(mutate(opts)).resolves.toMatchObject({
          errors: undefined,
          data: {
            write: {
              id: expect.any(String),
              title: 'New post',
              author: { id: 'alice' }
            }
          }
        })
      })
    })
  })
})

@JaapWeijland
Copy link

Any plans to implement this? I'm also experiencing testing limitations due to this issue

@glasser
Copy link
Member

glasser commented Feb 24, 2021

apollo-server-testing is a pretty thin wrapper around executeOperation, which is basically a method that lets you execute a GraphQL operation without going through the HTTP layer.

We're currently figuring out what parts of Apollo Server are really essential and which could be pared down in the upcoming AS3. My instincts are that apollo-server-testing doesn't provide a huge amount of standalone value, and that we should document the direct use of executeOperation for tests that don't want to test things that require the HTTP layer (like headers and cookies), and that we should encourage folks to use https://github.com/zapier/apollo-server-integration-testing for tests that require the HTTP layer (at least if you're using Express!). I'm tracking this in #4952.

I don't think it's likely that we'll change the existing apollo-server-testing to work differently during v2 (ie, we're probably not going to add anything Express-specific to it), so I'm going to close this issue. It's great to see that there are so many approaches to doing these tests, including apollo-server-integration-testing!

@glasser glasser closed this as completed Feb 24, 2021
deduced added a commit to thebrutalcorporation/spensa that referenced this issue May 10, 2021
You can't really write real integration tests with apollo-server-testing, because it doesn't support servers which rely on the context option being a function that uses the req object despite this functionality being supported by the real apollo server.

The official integration example code from Apollo solves this by instantiating an ApolloServer inside the test and mocking the context value by hand. But I don't consider this a real integration test, since you're not using the same instantiation code that your production code uses.

* description of issue with apollo-server-testing: apollographql/apollo-server#2277
* workaround: https://github.com/apollographql/fullstack-tutorial/blob/6988f6948668ccc2dea3f7a216dd44bdf25a0b9f/final/server/src/__tests__/integration.js#L68-L74
deduced added a commit to thebrutalcorporation/spensa that referenced this issue May 10, 2021
Previously, the apollo-server-testing was used for the tests. This had key limitations, such as non-access to context without a low-confidence workaround (see apollographql/apollo-server#2277 fof more info). This limitation also made creating some tests, such as ?should return current user if logged in" impossible. To resolve, migrate to https://github.com/zapier/apollo-server-integration-testing and create the tests that were previously impossible.

* Update queries-mutations constant with queries/mutations for new tests
* refactor the user.test file to remove old approach
* add missing test coverage
deduced added a commit to thebrutalcorporation/spensa that referenced this issue May 11, 2021
…pendency

https://github.com/zapier/apollo-server-integration-testing since it allows for high confidence testing. The problems with the apollo-testing can be found here: apollographql/apollo-server#2277 and are commented in other commit messages as well.
@glasser glasser removed this from the Release 3.x milestone Jun 10, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.