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

setOptions after creation and more specific types #3

Merged
merged 2 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ const UPDATE_USER = `
}
`;

const mutationResult = await mutate(
UPDATE_USER,
{ variables: { id: 1, email: 'nancy@foo.co' } }
);
const mutationResult = await mutate(UPDATE_USER, {
variables: { id: 1, email: 'nancy@foo.co' }
});

expect(mutationResult).toEqual({
data: {
Expand Down Expand Up @@ -85,6 +84,34 @@ This is useful when your apollo server `context` option is a callback that opera

As mentioned above, if you don't pass an `extendMockRequest` to `createTestClient`, we provide a default request mock object for you. See https://github.com/howardabrams/node-mocks-http#createrequest for all the default values that are included in that mock.

### setOptions

You can also set the `request` and `response` mocking options **after** the creation of the `test client`, which is a **cleaner** and **faster** way due not needing to create a new instance for **any** change you might want to do the `request` or `response`.

```js
const { query, setOptions } = createTestClient({
apolloServer
});

setOptions({
// If "request" or "response" is not specified, it's not modified
request: {
headers: {
cookie: 'csrf=blablabla',
referer: ''
}
},
response: {
locals: {
user: {
isAuthenticated: false
}
}
}
});
```


## Why not use `apollo-server-testing`?

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 ([see this issue for more information](https://github.com/apollographql/apollo-server/issues/2277)).
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
"dev": "tsc --watch"
},
"dependencies": {
"apollo-server-core": "^2.8.1",
"apollo-server-express": "^2.8.1",
"apollo-server-core": "^2.9.13",
"apollo-server-express": "^2.9.13",
"express": "^4.17.1",
"node-mocks-http": "^1.7.6",
"typescript": "^3.5.3"
"node-mocks-http": "^1.8.0"
},
"devDependencies": {
"typescript": "^3.7.3"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"
Expand Down
76 changes: 56 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
// @flow

import express from 'express';
import httpMocks from 'node-mocks-http';
import { print } from 'graphql';
import { convertNodeHttpToRequest, runHttpQuery } from 'apollo-server-core';

import { ApolloServer } from 'apollo-server-express';
import { DocumentNode } from 'graphql';
import express from 'express';
import { DocumentNode, print } from 'graphql';
import httpMocks, { RequestOptions, ResponseOptions } from 'node-mocks-http';

const mockRequest = (options = {}) =>
const mockRequest = (options: RequestOptions = {}) =>
httpMocks.createRequest({
method: 'POST',
...options
});

const mockResponse = (options = {}) => httpMocks.createResponse(options);
const mockResponse = (options: ResponseOptions = {}) =>
httpMocks.createResponse(options);

type StringOrAst = string | DocumentNode;
type Options = { variables?: object};
export type StringOrAst = string | DocumentNode;
export type Options<T extends object> = { variables?: T };

type TestClientConfig = {
export type TestClientConfig = {
// The ApolloServer instance that will be used for handling the queries you run in your tests.
// Must be an instance of the ApolloServer class from `apollo-server-express` (or a compatible subclass).
apolloServer: ApolloServer;
Expand All @@ -28,15 +27,25 @@ type TestClientConfig = {
// and you want to inject data into that `req` object.
// If you don't pass anything here, we provide a default request mock object for you.
// See https://github.com/howardabrams/node-mocks-http#createrequest for all the default values that are included.
extendMockRequest?: {};
extendMockRequest?: RequestOptions;
// Extends the mocked Response object with additional keys.
// Useful when your apolloServer `context` option is a callback that operates on the passed in `res` key,
// and you want to inject data into that `res` object (such as `res.locals`).
// If you don't pass anything here, we provide a default response mock object for you.
// See https://www.npmjs.com/package/node-mocks-http#createresponse for all the default values that are included.
extendMockResponse?: {};
extendMockResponse?: ResponseOptions;
};

export type TestQuery = <T extends object = {}, V extends object = {}>(
operation: StringOrAst,
{ variables }?: Options<V>
) => Promise<T>;

export type TestSetOptions = (options: {
request?: RequestOptions;
response?: ResponseOptions;
}) => void;

// This function takes in an apollo server instance and returns a function that you can use to run operations
// against your schema, and assert on the results.
//
Expand All @@ -59,17 +68,43 @@ type TestClientConfig = {
// }
// });
// ```
export const createTestClient = ({
export function createTestClient({
apolloServer,
extendMockRequest = {},
extendMockResponse = {}
}: TestClientConfig) => {
}: TestClientConfig) {
const app = express();
apolloServer.applyMiddleware({ app });

const test = async (operation: StringOrAst, {variables}: Options = {}) => {
const req = mockRequest(extendMockRequest);
const res = mockResponse(extendMockResponse);
let mockRequestOptions = extendMockRequest;
let mockResponseOptions = extendMockResponse;

/**
* Set the options after TestClient creation
* Useful when you don't want to create a new instance just for a specific change in the request or response.
* */

const setOptions: TestSetOptions = ({
request,
response
}: {
request?: RequestOptions;
response?: ResponseOptions;
}) => {
if (request) {
mockRequestOptions = request;
}
if (response) {
mockResponseOptions = response;
}
};

const test: TestQuery = async <T extends object = {}, V extends object = {}>(
operation: StringOrAst,
{ variables }: Options<V> = {}
) => {
const req = mockRequest(mockRequestOptions);
const res = mockResponse(mockResponseOptions);

const graphQLOptions = await apolloServer.createGraphQLServerOptions(
req,
Expand All @@ -87,11 +122,12 @@ export const createTestClient = ({
request: convertNodeHttpToRequest(req)
});

return JSON.parse(graphqlResponse);
return JSON.parse(graphqlResponse) as T;
};

return {
query: test,
mutate: test
mutate: test,
setOptions
};
};
}
Loading