zapier / apollo-server-integration-testing

Test helper for writing apollo-server integration tests
MIT License
134 stars 21 forks source link

How to test middleware? #18

Open riggedCoinflip opened 3 years ago

riggedCoinflip commented 3 years ago

I am searching for a way to test my authorization middleware.

I use express middleware to validate my authorization token.

//isAuth
//simplified
module.exports = (req, res, next) => {
    const token = req.headers['x-auth-token'];
    if (token invalid) {
        req.user = {
            isAuth: false,
        }
    else {
        req.user = {
            isAuth: true,
            _id: decodedToken._id,
            name: decodedToken.username,
            role: decodedToken.role,
    }
    next();
}

I then apply the middleware

const app = express();
app.use(isAuth);

function createApollo() {
    const apollo = new ApolloServer({
        schema: graphqlSchema,
        context: ({req, res}) => ({req, res}),
    });
    apollo.applyMiddleware({app, path: "/graphql"});
    return apollo
}

Lastly, I wrap my graphql-compose resolvers that require authentication with this function:

module.exports = (resolvers) => {
    Object.keys(resolvers).forEach((k) => {
        resolvers[k] = resolvers[k].wrapResolve(next => async rp => {
            if (!rp.context.req.user.isAuth) {
                throw new Error('You must login to view this.');
            }
            return next(rp)
        })
    })
    return resolvers
}

In the end I got it working like this:

setOptions({
    request: {
        user: {
            isAuth: true,
            _id: decodedToken._id,
            name: decodedToken.username,
            role: decodedToken.role,
        }
    },
});

but that bypasses my isAuth middleware.

Is there any way, using this or any other package to test middleware as well? We could add apollo-client or alike as a dev-dependency and test the queries as if there were directly from frontend, but there has to be a better way.

deduced commented 3 years ago

I am having a very similar issue, but in my case I am trying to mock the isAuth and call next() as a mock implementation, but I don't think it's being made available in the test. https://stackoverflow.com/questions/67768435/proper-way-to-test-type-graphql-middleware-with-jest.

deduced commented 3 years ago

@riggedCoinflip have you tried mocking the import? https://stackoverflow.com/questions/60722281/testing-an-express-middleware-with-jest?

jest.mock("pathToYourAuthModule", () => {
  return {
    isAuth: jest.fn((req,res,next) => next()), //and/or you can set your req.user.isAuth to true
  };
});

if your authentication module has other functionality you want to keep unmocked, see https://jestjs.io/docs/jest-object#jestrequireactualmodulename

nogashimoni commented 3 years ago

I'm having the same issue. Why aren't express middlewares applied? Is this by design or is it a bug?

vitorbal commented 3 years ago

@nogashimoni @riggedCoinflip howdy folks! It looks like this is a bug with the way this library simulates the graphql requests. I have a failing test that reproduces the issue, so I'll be looking into possible solutions this week.

vitorbal commented 3 years ago

So I've been taking another look at this and I don't think it will be possible to solve it without a couple of breaking changes to the API. Specifically, we'd have to make it so this library starts the graphql server and then sends actual http requests to it. Today, we use some of the ApolloServer APIs to simulate a graphql request instead.

@nogashimoni @riggedCoinflip one alternative would be to refactor your middlewares so they can run from within the context callback, like this:

const apolloServer = new ApolloServer({
  schema,
  context: ({ req }) => {
    return isAuth(req);
  },
});

If that's not okay, then I would recommend looking into a library like supertest that help you test an http server.

Meanwhile, I'm going to do some thinking on what the future of this library is. ApolloServer now exposes an executeOperation API that should make it easier to solve what this library initially set out to solve, but also wouldn't support testing express middlewares. Perhaps we can rework this library to provide sugar on top of executeOperation, and additionally provide an API to help test apollo-server-express end-to-end too.