diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ada8b0bd8..548ed518b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The version headers in this history reflect the versions of Apollo Server itself - `apollo-server-lambda`: The handler returned by `createHandler` can now only be called as an async function returning a `Promise` (it no longer optionally accepts a callback as the third argument). All current Lambda Node runtimes support this invocation mode (so `exports.handler = server.createHandler()` will keep working without any changes), but if you've written your own handler which calls the handler returned by `createHandler` with a callback, you'll need to handle its `Promise` return value instead. - The `tracing` option to `new ApolloServer` has been removed, and the `apollo-server-tracing` package has been deprecated and is no longer being published. This package implemented an inefficient JSON format for execution traces returned on the `tracing` GraphQL response extension; it was only consumed by the deprecated `engineproxy` and Playground. If you really need this format, the old version of `apollo-server-tracing` should still work (`new ApolloServer({plugins: [require('apollo-server-tracing').plugin()]})`). - The `cacheControl` option to `new ApolloServer` has been removed. The functionality provided by `cacheControl: true` or `cacheControl: {stripFormattedExtensions: false}` (which included a `cacheControl` extension in the GraphQL response, for use by the deprecated `engineproxy`) has been entirely removed. The default behavior of Apollo Server continues to be calculating an overall cache policy and setting the `Cache-Control` HTTP header, but this is now implemented directly inside `apollo-server-core` rather than a separate `apollo-cache-control` package (this package has been deprecated and is no longer being published). Tweaking cache control settings like `defaultMaxAge` is now done via the newly exported `ApolloServerPluginCacheControl` plugin rather than as a top-level constructor option. This follows the same pattern as the other built-in plugins like usage reporting. The `CacheHint` and `CacheScope` types are now exported from `apollo-server-types`. +- When using a non-serverless framework integration (Express, Fastify, Hapi, Koa, Micro, or Cloudflare), you now *must* `await server.start()` before attaching the server to your framework. (This method was introduced in v2.22 but was optional before Apollo Server 3.) This does not apply to the batteries-included `apollo-server` or to serverless framework integrations. - Top-level exports have changed. E.g., - We no longer re-export the entirety of `graphql-tools` (including `makeExecutableSchema`) from all Apollo Server packages. If you'd like to continue using them, install [`graphql-tools`](https://www.graphql-tools.com/) or one of its sub-packages yourself. diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index 36223e225a3..2cb0dadcfd6 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -659,6 +659,8 @@ The async `start` method instructs Apollo Server to prepare to handle incoming o Always call `await server.start()` *before* calling `server.applyMiddleware` and starting your HTTP server. This allows you to react to Apollo Server startup failures by crashing your process instead of starting to serve traffic. +This method was optional in Apollo Server 2 but is required in Apollo Server 3. + ##### Triggered actions The `start` method triggers the following actions: @@ -666,12 +668,6 @@ The `start` method triggers the following actions: 1. If your server is a [federated gateway](https://www.apollographql.com/docs/federation/managed-federation/overview/), it attempts to fetch its schema. If the fetch fails, `start` throws an error. 2. Your server calls all of the [`serverWillStart` handlers](../integrations/plugins/#serverwillstart) of your installed plugins. If any of these handlers throw an error, `start` throws an error. -##### Backward compatibility - -To ensure backward compatibility, calling `await server.start()` is optional. If you don't call it yourself, your integration package invokes it when you call `server.applyMiddleware`. Incoming GraphQL operations wait to execute until Apollo Server has started, and those operations fail if startup fails (a redacted error message is sent to the GraphQL client). - -We recommend calling `await server.start()` yourself, so that your web server doesn't start accepting GraphQL requests until Apollo Server is ready to process them. - #### `applyMiddleware` Connects Apollo Server to the HTTP framework of a Node.js middleware library, such as hapi or express. diff --git a/packages/apollo-server-azure-functions/src/__tests__/azureFunctionApollo.test.ts b/packages/apollo-server-azure-functions/src/__tests__/azureFunctionApollo.test.ts index 534afc5e9d4..15f9ebca05f 100644 --- a/packages/apollo-server-azure-functions/src/__tests__/azureFunctionApollo.test.ts +++ b/packages/apollo-server-azure-functions/src/__tests__/azureFunctionApollo.test.ts @@ -7,7 +7,7 @@ import { Config } from 'apollo-server-core'; import url from 'url'; import { IncomingMessage, ServerResponse } from 'http'; -const createAzureFunction = (options: CreateAppOptions = {}) => { +const createAzureFunction = async (options: CreateAppOptions = {}) => { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); @@ -51,7 +51,7 @@ const createAzureFunction = (options: CreateAppOptions = {}) => { }; describe('integration:AzureFunctions', () => { - testSuite(createAzureFunction); + testSuite({createApp: createAzureFunction, serverlessFramework: true}); it('can append CORS headers to GET request', async () => { const server = new ApolloServer({ schema: Schema }); diff --git a/packages/apollo-server-cloud-functions/src/__tests__/googleCloudApollo.test.ts b/packages/apollo-server-cloud-functions/src/__tests__/googleCloudApollo.test.ts index 22ab41668b2..bec00817329 100644 --- a/packages/apollo-server-cloud-functions/src/__tests__/googleCloudApollo.test.ts +++ b/packages/apollo-server-cloud-functions/src/__tests__/googleCloudApollo.test.ts @@ -26,7 +26,7 @@ const simulateGcfMiddleware = ( next(); }; -const createCloudFunction = (options: CreateAppOptions = {}) => { +const createCloudFunction = async (options: CreateAppOptions = {}) => { const handler = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ).createHandler(); @@ -49,5 +49,5 @@ describe('googleCloudApollo', () => { }); describe('integration:CloudFunction', () => { - testSuite(createCloudFunction); + testSuite({createApp: createCloudFunction, serverlessFramework: true}); }); diff --git a/packages/apollo-server-cloudflare/src/ApolloServer.ts b/packages/apollo-server-cloudflare/src/ApolloServer.ts index 8db9eb3206a..e4504faaa5a 100644 --- a/packages/apollo-server-cloudflare/src/ApolloServer.ts +++ b/packages/apollo-server-cloudflare/src/ApolloServer.ts @@ -14,10 +14,7 @@ export class ApolloServer extends ApolloServerBase { } public async listen() { - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('listen'); addEventListener('fetch', (event: FetchEvent) => { event.respondWith( diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index a8d6f86594b..7f39675422b 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -274,10 +274,8 @@ export class ApolloServerBase { if (gateway) { // ApolloServer has been initialized but we have not yet tried to load the // schema from the gateway. That will wait until the user calls - // `server.start()`, or until `ensureStarting` or `ensureStarted` are - // called. (In the case of a serverless framework integration, - // `ensureStarting` is automatically called at the end of the - // constructor.) + // `server.start()` or `server.listen()`, or (in serverless frameworks) + // until the `this._start()` call at the end of this constructor. this.state = { phase: 'initialized with gateway', gateway }; // The main thing that the Gateway does is replace execution with @@ -306,12 +304,12 @@ export class ApolloServerBase { // unlike (eg) applyMiddleware, so we can't expect you to `await // server.start()` before calling it. So we kick off the start // asynchronously from the constructor, and failures are logged and cause - // later requests to fail (in ensureStarted, called by - // graphQLServerOptions). There's no way to make "the whole server fail" + // later requests to fail (in `ensureStarted`, called by + // `graphQLServerOptions`). There's no way to make "the whole server fail" // separately from making individual requests fail, but that's not entirely // unreasonable for a "serverless" model. if (this.serverlessFramework()) { - this.ensureStarting(); + this._start().catch((e) => this.logStartupError(e)); } } @@ -324,35 +322,22 @@ export class ApolloServerBase { // `listen` method takes care of that for you (this is why the actual logic is // in the `_start` helper). // - // If instead you're using an integration package, you are highly encouraged - // to await a call to `start` immediately after creating your `ApolloServer`, - // before attaching it to your web framework and starting to accept requests. - // `start` should only be called once; if it throws and you'd like to retry, - // just create another `ApolloServer`. (Note that this paragraph does not - // apply to "serverless framework" integrations like Lambda.) - // - // For backwards compatibility with the pre-2.22 API, you are not required to - // call start() yourself (this may change in AS3). Most integration packages - // call the protected `ensureStarting` when you first interact with them, - // which kicks off a "background" call to `start` if you haven't called it - // yourself. Then `graphQLServerOptions` (which is called before processing) - // each incoming GraphQL request) calls `ensureStarted` which waits for - // `start` to successfully complete (possibly by calling it itself), and - // throws a redacted error if `start` was not successful. If `start` is - // invoked implicitly by either of these mechanisms, any error that it throws - // will be logged when they occur and then again on every subsequent - // `graphQLServerOptions` call (ie, every GraphQL request). Note that start - // failures are not recoverable without creating a new ApolloServer. You are - // highly encouraged to make these backwards-compatibility paths into no-ops - // by awaiting a call to `start` yourself. + // If instead you're using an integration package for a non-serverless + // framework (like Express), you must await a call to `start` immediately + // after creating your `ApolloServer`, before attaching it to your web + // framework and starting to accept requests. `start` should only be called + // once; if it throws and you'd like to retry, just create another + // `ApolloServer`. (Calling `start` was optional in Apollo Server 2, but in + // Apollo Server 3 the methods like `server.applyMiddleware` use + // `assertStarted` to throw if `start` hasn't successfully completed.) // // Serverless integrations like Lambda (which override `serverlessFramework()` // to return true) do not support calling `start()`, because their lifecycle // doesn't allow you to wait before assigning a handler or allowing the - // handler to be called. So they call `ensureStarting` at the end of the + // handler to be called. So they call `_start()` at the end of the // constructor, and don't really differentiate between startup failures and // request failures. This is hopefully appropriate for a "serverless" - // framework. As above, startup failures result in returning a redacted error + // framework. Serverless startup failures result in returning a redacted error // to the end user and logging the more detailed error. public async start(): Promise { if (this.serverlessFramework()) { @@ -443,55 +428,25 @@ export class ApolloServerBase { } } - /** - * @deprecated This deprecated method is provided for backwards compatibility - * with the pre-v2.22 API. It was sort of a combination of the v2.22 APIs - * `ensureStarting` and `start`; it was generally called "in the background" - * by integrations to kick off the start process (like `ensureStarting`) and - * then the Promise it returns was awaited later before running operations - * (sort of like `start`). It had odd error handling semantics, in that it - * would ignore any error that came from loading the schema, but would throw - * errors that came from `serverWillStart`. - * - * We keep it around for backwards-compatibility with pre-v2.22 integrations, - * though we just make it call `ensureStarting`. This does mean that the part - * of the integration which awaits its result doesn't actually await anything - * interesting (despite being async, the method itself doesn't await - * anything), but since executing operations now calls `ensureStarted`, that's - * OK. (In v2.22.0 and v2.22.1 we tried to mimic the old `willStart` behavior - * more closely which led to a bug where `start` could be invoked multiple - * times. This approach is simpler.) - * - * Anyone calling this method should call `start` or `ensureStarting` instead. - */ - protected async willStart() { - this.ensureStarting(); - } - - // Part of the backwards-compatibility behavior described above `start` to - // make ApolloServer work if you don't explicitly call `start`, as well as for - // serverless frameworks where there is no `start`. This is called at the - // beginning of each GraphQL request by `graphQLServerOptions`. It calls - // `start` for you if it hasn't been called yet, and only returns successfully - // if some call to `start` succeeds. - // - // This function assumes it is being called in a context where any error it - // throws may be shown to the end user, so it only throws specific errors - // without details. If it's throwing due to a startup error, it will log that - // error each time it is called before throwing a redacted error. + // This method is called at the beginning of each GraphQL request by + // `graphQLServerOptions`. Most of its logic is only helpful for serverless + // frameworks: unless you're in a serverless framework, you should have called + // `await server.start()` before the server got to the point of running + // GraphQL requests (`assertStarted` calls in the framework integrations + // verify that) and so the only cases for non-serverless frameworks that this + // should hit are 'started', 'stopping', and 'stopped'. For serverless + // frameworks, this lets the server wait until fully started before serving + // operations. private async ensureStarted(): Promise { while (true) { switch (this.state.phase) { case 'initialized with gateway': case 'initialized with schema': - try { - await this._start(); - } catch { - // Any thrown error should transition us to 'failed to start', and - // we'll handle that on the next iteration of the while loop. - } - // continue the while loop - break; + // This error probably won't happen: serverless frameworks + // automatically call `_start` at the end of the constructor, and + // other frameworks call `assertStarted` before setting things up + // enough to make calling this function possible. + throw new Error("You need to call `server.start()` before using your Apollo Server."); case 'starting': case 'invoking serverWillStart': await this.state.barrier; @@ -523,51 +478,26 @@ export class ApolloServerBase { } } - // Part of the backwards-compatibility behavior described above `start` to - // make ApolloServer work if you don't explicitly call `start`. This is called - // by some of the integration frameworks when you interact with them (eg by - // calling applyMiddleware). It is also called from the end of the constructor - // for serverless framework integrations. - // - // It calls `start` for you if it hasn't been called yet, but doesn't wait for - // `start` to finish. The goal is that if you don't call `start` yourself the - // server should still do the rest of startup vaguely near when your server - // starts, not just when the first GraphQL request comes in. Without this - // call, startup wouldn't occur until `graphQLServerOptions` invokes - // `ensureStarted`. - protected ensureStarting() { - if ( - this.state.phase === 'initialized with gateway' || - this.state.phase === 'initialized with schema' - ) { - // Ah well. It would have been nice if the user had bothered - // to call and await `start()`; that way they'd be able to learn - // about any errors from it. Instead we'll kick it off here. - // Any thrown error will get logged, and also will cause - // every call to ensureStarted (ie, every GraphQL operation) - // to log it again and prevent the operation from running. - this._start().catch((e) => this.logStartupError(e)); + protected assertStarted(methodName: string) { + if (this.state.phase !== 'started') { + throw new Error( + 'You must `await server.start()` before calling `server.' + + methodName + + '()`', + ); } + // XXX do we need to do anything special for stopping/stopped? } // Given an error that occurred during Apollo Server startup, log it with a - // helpful message. Note that this is only used if `ensureStarting` or - // `ensureStarted` had to initiate the startup process; if you call - // `start` yourself (or you're using `apollo-server` whose `listen()` does - // it for you) then you can handle the error however you'd like rather than - // this log occurring. (We don't suggest the use of `start()` for serverless - // frameworks because they don't support it.) + // helpful message. This should only happen with serverless frameworks; with + // other frameworks, you must `await server.start()` which will throw the + // startup error directly instead of logging (or `await server.listen()` for + // the batteries-included `apollo-server`). private logStartupError(err: Error) { - const prelude = this.serverlessFramework() - ? 'An error occurred during Apollo Server startup.' - : 'Apollo Server was started implicitly and an error occurred during startup. ' + - '(Consider calling `await server.start()` immediately after ' + - '`server = new ApolloServer()` so you can handle these errors directly before ' + - 'starting your web server.)'; this.logger.error( - prelude + - ' All GraphQL requests will now fail. The startup error ' + - 'was: ' + + 'An error occurred during Apollo Server startup. All GraphQL requests ' + + 'will now fail. The startup error was: ' + ((err && err.message) || err), ); } @@ -704,7 +634,9 @@ export class ApolloServerBase { return false; } - private ensurePluginInstantiation(userPlugins: PluginDefinition[] = []): void { + private ensurePluginInstantiation( + userPlugins: PluginDefinition[] = [], + ): void { this.plugins = userPlugins.map((plugin) => { if (typeof plugin === 'function') { return plugin(); diff --git a/packages/apollo-server-core/src/__tests__/ApolloServerBase.test.ts b/packages/apollo-server-core/src/__tests__/ApolloServerBase.test.ts index 90ffadda705..ae851458c91 100644 --- a/packages/apollo-server-core/src/__tests__/ApolloServerBase.test.ts +++ b/packages/apollo-server-core/src/__tests__/ApolloServerBase.test.ts @@ -87,7 +87,9 @@ describe('ApolloServerBase start', () => { await expect(server.start()).rejects.toThrow('nope'); }); - it('execute throws redacted message on implicit startup error', async () => { + // This is specific to serverless because on server-ful frameworks, you can't + // get to executeOperation without server.start(). + it('execute throws redacted message on serverless startup error', async () => { const error = jest.fn(); const logger: Logger = { debug: jest.fn(), @@ -96,28 +98,36 @@ describe('ApolloServerBase start', () => { error, }; - const server = new ApolloServerBase({ + class ServerlessApolloServer extends ApolloServerBase { + serverlessFramework() { + return true; + } + } + + const server = new ServerlessApolloServer({ typeDefs, resolvers, plugins: [failToStartPlugin], logger, }); - // Run the operation twice (the first will kick off the start process). We - // want to see the same error thrown and log message for the "kick it off" - // call as the subsequent call. + // Run the operation twice. We want to see the same error thrown and log + // message for the "kick it off" call as the subsequent call. await expect( server.executeOperation({ query: '{__typename}' }), ).rejects.toThrow(redactedMessage); await expect( server.executeOperation({ query: '{__typename}' }), ).rejects.toThrow(redactedMessage); - expect(error).toHaveBeenCalledTimes(2); - expect(error.mock.calls[0][0]).toMatch( - /Apollo Server was started implicitly.*nope/, - ); - expect(error.mock.calls[1][0]).toMatch( - /Apollo Server was started implicitly.*nope/, - ); + + // Three times: once for the actual background _start call, twice for the + // two operations. + expect(error).toHaveBeenCalledTimes(3); + for (const [message] of error.mock.calls) { + expect(message).toBe( + 'An error occurred during Apollo Server startup. All ' + + 'GraphQL requests will now fail. The startup error was: nope', + ); + } }); }); @@ -127,6 +137,7 @@ describe('ApolloServerBase executeOperation', () => { typeDefs, resolvers, }); + await server.start(); const result = await server.executeOperation({ query: 'query { error }' }); @@ -142,6 +153,7 @@ describe('ApolloServerBase executeOperation', () => { resolvers, debug: true, }); + await server.start(); const result = await server.executeOperation({ query: 'query { error }' }); diff --git a/packages/apollo-server-core/src/__tests__/dataSources.test.ts b/packages/apollo-server-core/src/__tests__/dataSources.test.ts index 995ef2ee497..74e9c0e373b 100644 --- a/packages/apollo-server-core/src/__tests__/dataSources.test.ts +++ b/packages/apollo-server-core/src/__tests__/dataSources.test.ts @@ -22,6 +22,7 @@ describe('ApolloServerBase dataSources', () => { }, dataSources: () => ({ x: { initialize }, y: { initialize } }) }); + await server.start(); await server.executeOperation({ query: "query { hello }"}); @@ -75,6 +76,7 @@ describe('ApolloServerBase dataSources', () => { } }) }); + await server.start(); await server.executeOperation({ query: "query { hello }"}); @@ -96,6 +98,7 @@ describe('ApolloServerBase dataSources', () => { }, dataSources: () => ({ x: { initialize() {}, getData } }) }); + await server.start(); const res = await server.executeOperation({ query: "query { hello }"}); diff --git a/packages/apollo-server-core/src/__tests__/logger.test.ts b/packages/apollo-server-core/src/__tests__/logger.test.ts index 5355ffa1a62..09eeb14765f 100644 --- a/packages/apollo-server-core/src/__tests__/logger.test.ts +++ b/packages/apollo-server-core/src/__tests__/logger.test.ts @@ -14,7 +14,7 @@ const LOWEST_LOG_LEVEL = "debug"; const KNOWN_DEBUG_MESSAGE = "The request has started."; async function triggerLogMessage(loggerToUse: Logger) { - await (new ApolloServerBase({ + const server = new ApolloServerBase({ typeDefs: gql` type Query { field: String! @@ -25,11 +25,13 @@ async function triggerLogMessage(loggerToUse: Logger) { { requestDidStart({ logger }) { logger.debug(KNOWN_DEBUG_MESSAGE); - } - } - ] - })).executeOperation({ - query: '{ field }' + }, + }, + ], + }); + await server.start(); + await server.executeOperation({ + query: '{ field }', }); } diff --git a/packages/apollo-server-express/src/ApolloServer.ts b/packages/apollo-server-express/src/ApolloServer.ts index ee973c32130..fdc4d29c7a9 100644 --- a/packages/apollo-server-express/src/ApolloServer.ts +++ b/packages/apollo-server-express/src/ApolloServer.ts @@ -65,6 +65,9 @@ export class ApolloServer extends ApolloServerBase { } public applyMiddleware({ app, ...rest }: ServerRegistration) { + // getMiddleware calls this too, but we want the right method name in the error + this.assertStarted('applyMiddleware'); + app.use(this.getMiddleware(rest)); } @@ -80,10 +83,7 @@ export class ApolloServer extends ApolloServerBase { }: GetMiddlewareOptions = {}): express.Router { if (!path) path = '/graphql'; - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('getMiddleware'); const router = express.Router(); diff --git a/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts index 2808bd37601..29707489395 100644 --- a/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-express/src/__tests__/ApolloServer.test.ts @@ -33,9 +33,9 @@ describe('apollo-server-express', () => { let server: ApolloServer; let httpServer: http.Server; testApolloServer( - async (options: ApolloServerExpressConfig, suppressStartCall?: boolean) => { - server = new ApolloServer(options); - if (!suppressStartCall) { + async (config: ApolloServerExpressConfig, options) => { + server = new ApolloServer(config); + if (!options?.suppressStartCall) { await server.start(); } const app = express(); @@ -63,8 +63,8 @@ describe('apollo-server-express', () => { options: Partial = {}, ) { server = new ApolloServer({stopOnTerminationSignals: false, ...serverOptions}); + await server.start(); app = express(); - server.applyMiddleware({ ...options, app }); httpServer = await new Promise(resolve => { @@ -191,6 +191,7 @@ describe('apollo-server-express', () => { resolvers, playground: true, }); + await rewiredServer.start(); const innerApp = express(); rewiredServer.applyMiddleware({ app: innerApp }); diff --git a/packages/apollo-server-express/src/__tests__/connectApollo.test.ts b/packages/apollo-server-express/src/__tests__/connectApollo.test.ts index 85abb240ea9..a4b9ade2bfa 100644 --- a/packages/apollo-server-express/src/__tests__/connectApollo.test.ts +++ b/packages/apollo-server-express/src/__tests__/connectApollo.test.ts @@ -7,7 +7,7 @@ import testSuite, { CreateAppOptions, } from 'apollo-server-integration-testsuite'; -function createConnectApp(options: CreateAppOptions = {}) { +async function createConnectApp(options: CreateAppOptions = {}) { const app = connect(); // We do require users of ApolloServer with connect to use a query middleware // first. The alternative is to add a 'isConnect' bool to ServerRegistration @@ -18,11 +18,12 @@ function createConnectApp(options: CreateAppOptions = {}) { const server = new ApolloServer( (options.graphqlOptions as ApolloServerExpressConfig) || { schema: Schema }, ); + await server.start(); // See comment on ServerRegistration.app for its typing. server.applyMiddleware({ app: app as any }); return app; } describe('integration:Connect', () => { - testSuite(createConnectApp); + testSuite({createApp: createConnectApp}); }); diff --git a/packages/apollo-server-express/src/__tests__/datasource.test.ts b/packages/apollo-server-express/src/__tests__/datasource.test.ts index d9927164b4a..b9e518b157e 100644 --- a/packages/apollo-server-express/src/__tests__/datasource.test.ts +++ b/packages/apollo-server-express/src/__tests__/datasource.test.ts @@ -101,6 +101,7 @@ describe('apollo-server-express', () => { }, }), }); + await server.start(); const app = express(); server.applyMiddleware({ app }); @@ -133,6 +134,7 @@ describe('apollo-server-express', () => { }, }), }); + await server.start(); const app = express(); server.applyMiddleware({ app }); diff --git a/packages/apollo-server-express/src/__tests__/expressApollo.test.ts b/packages/apollo-server-express/src/__tests__/expressApollo.test.ts index f1bb2cab450..f2a6c38bc50 100644 --- a/packages/apollo-server-express/src/__tests__/expressApollo.test.ts +++ b/packages/apollo-server-express/src/__tests__/expressApollo.test.ts @@ -5,12 +5,13 @@ import testSuite, { CreateAppOptions, } from 'apollo-server-integration-testsuite'; -function createApp(options: CreateAppOptions = {}) { +async function createApp(options: CreateAppOptions = {}) { const app = express(); const server = new ApolloServer( (options.graphqlOptions as ApolloServerExpressConfig) || { schema: Schema }, ); + await server.start(); server.applyMiddleware({ app }); return app; } @@ -24,5 +25,5 @@ describe('expressApollo', () => { }); describe('integration:Express', () => { - testSuite(createApp); + testSuite({createApp}); }); diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts index fc73e5be0ec..9e0a36fd5c5 100644 --- a/packages/apollo-server-fastify/src/ApolloServer.ts +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -42,10 +42,7 @@ export class ApolloServer extends ApolloServerBase { }: ServerRegistration = {}) { this.graphqlPath = path || '/graphql'; - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('createHandler'); return async (app: FastifyInstance) => { if (!disableHealthCheck) { diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts index 8591be8cfe9..5554670a366 100644 --- a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -34,9 +34,9 @@ describe('apollo-server-fastify', () => { let app: FastifyInstance; testApolloServer( - async (options: any, suppressStartCall?: boolean) => { - server = new ApolloServer(options); - if (!suppressStartCall) { + async (config: any, options) => { + server = new ApolloServer(config); + if (!options?.suppressStartCall) { await server.start(); } app = fastify(); @@ -65,6 +65,7 @@ describe('apollo-server-fastify', () => { mockDecorators: boolean = false, ) { server = new ApolloServer({ stopOnTerminationSignals: false, ...serverOptions }); + await server.start(); app = fastify(); if (mockDecorators) { diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts index 887012057be..3f291339f9a 100644 --- a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -13,6 +13,7 @@ async function createApp(options: CreateAppOptions = {}) { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); + await server.start(); app.register(server.createHandler()); await app.listen(0); @@ -36,5 +37,5 @@ describe('fastifyApollo', () => { }); describe('integration:Fastify', () => { - testSuite(createApp, destroyApp); + testSuite({createApp, destroyApp}); }); diff --git a/packages/apollo-server-hapi/src/ApolloServer.ts b/packages/apollo-server-hapi/src/ApolloServer.ts index f14328eb081..62e74d9def3 100644 --- a/packages/apollo-server-hapi/src/ApolloServer.ts +++ b/packages/apollo-server-hapi/src/ApolloServer.ts @@ -34,10 +34,7 @@ export class ApolloServer extends ApolloServerBase { disableHealthCheck, onHealthCheck, }: ServerRegistration) { - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('applyMiddleware'); if (!path) path = '/graphql'; diff --git a/packages/apollo-server-hapi/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-hapi/src/__tests__/ApolloServer.test.ts index f982030f7b2..cbd10711175 100644 --- a/packages/apollo-server-hapi/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-hapi/src/__tests__/ApolloServer.test.ts @@ -23,10 +23,10 @@ describe( const { Server } = require('@hapi/hapi'); testApolloServer( - async (options: any, suppressStartCall?: boolean) => { - server = new ApolloServer(options); + async (config: any, options) => { + server = new ApolloServer(config); app = new Server({ host: 'localhost', port }); - if (!suppressStartCall) { + if (!options?.suppressStartCall) { await server.start(); } await server.applyMiddleware({ app }); @@ -63,6 +63,7 @@ describe( it('accepts typeDefs and resolvers', async () => { const app = new Server(); const server = new ApolloServer({ typeDefs, resolvers }); + await server.start(); return server.applyMiddleware({ app }); }); }); @@ -73,6 +74,7 @@ describe( typeDefs, resolvers, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ app }); @@ -107,6 +109,7 @@ describe( resolvers, introspection: false, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ app }); @@ -156,6 +159,7 @@ describe( resolvers, stopOnTerminationSignals: false, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ app }); @@ -193,6 +197,7 @@ describe( typeDefs, resolvers, }); + await server.start(); app = new Server({ port, }); @@ -233,6 +238,7 @@ describe( typeDefs, resolvers, }); + await server.start(); app = new Server({ port, }); @@ -284,6 +290,7 @@ describe( resolvers, context, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ app }); @@ -309,6 +316,7 @@ describe( typeDefs, resolvers, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ app }); @@ -341,6 +349,7 @@ describe( typeDefs, resolvers, }); + await server.start(); app = new Server({ port }); await server.applyMiddleware({ @@ -378,7 +387,7 @@ describe( typeDefs, resolvers, }); - + await server.start(); app = new Server({ port }); await server.applyMiddleware({ @@ -433,6 +442,7 @@ describe( throw new AuthenticationError('valid result'); }, }); + await server.start(); app = new Server({ port }); @@ -479,6 +489,7 @@ describe( }, stopOnTerminationSignals: false, }); + await server.start(); app = new Server({ port }); @@ -527,6 +538,7 @@ describe( }, stopOnTerminationSignals: false, }); + await server.start(); app = new Server({ port }); @@ -572,6 +584,7 @@ describe( }, stopOnTerminationSignals: false, }); + await server.start(); app = new Server({ port }); diff --git a/packages/apollo-server-hapi/src/__tests__/hapiApollo.test.ts b/packages/apollo-server-hapi/src/__tests__/hapiApollo.test.ts index 816184caf30..795c9409767 100644 --- a/packages/apollo-server-hapi/src/__tests__/hapiApollo.test.ts +++ b/packages/apollo-server-hapi/src/__tests__/hapiApollo.test.ts @@ -17,6 +17,7 @@ describe('integration:Hapi', () => { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); + await server.start(); await server.applyMiddleware({ app, }); @@ -33,5 +34,5 @@ describe('integration:Hapi', () => { await new Promise(resolve => app.close(resolve)); } - testSuite(createApp, destroyApp); + testSuite({createApp, destroyApp}); }); diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts index 84748b6889f..3ac0d1aecfb 100644 --- a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -45,7 +45,11 @@ import { } from 'apollo-server-core'; import { Headers, fetch } from 'apollo-server-env'; import ApolloServerPluginResponseCache from 'apollo-server-plugin-response-cache'; -import { BaseContext, GraphQLRequestContext, GraphQLRequestContextExecutionDidStart } from 'apollo-server-types'; +import { + BaseContext, + GraphQLRequestContext, + GraphQLRequestContextExecutionDidStart, +} from 'apollo-server-types'; import resolvable, { Resolvable } from '@josephg/resolvable'; import FakeTimers from '@sinonjs/fake-timers'; @@ -166,7 +170,9 @@ export interface ServerInfo { } export interface CreateServerFunc { - (config: Config, suppressStartCall?: boolean): Promise>; + (config: Config, options?: { suppressStartCall?: boolean }): Promise< + ServerInfo + >; } export interface StopServerFunc { @@ -396,54 +402,15 @@ export function testApolloServer( ).rejects.toThrowError(loadError); }); - it('rejected load promise acts as an error boundary', async () => { - const executor = jest.fn(); - executor.mockResolvedValueOnce({ - data: { testString: 'should not get this' }, - }); - - executor.mockRejectedValueOnce({ - errors: [{ errorWhichShouldNot: 'ever be triggered' }], - }); - - const consoleErrorSpy = jest - .spyOn(console, 'error') - .mockImplementation(); - - const { gateway, triggers } = makeGatewayMock({ executor }); - - triggers.rejectLoad(new Error('load error which should be masked')); - - const { url: uri } = await createApolloServer( - { - gateway, - }, - true, - ); - - const apolloFetch = createApolloFetch({ uri }); - const result = await apolloFetch({ query: '{testString}' }); - - expect(result.data).toBeUndefined(); - expect(result.errors).toContainEqual( - expect.objectContaining({ - extensions: expect.objectContaining({ - code: 'INTERNAL_SERVER_ERROR', - }), - message: - 'This data graph is missing a valid configuration. ' + - 'More details may be available in the server logs.', - }), - ); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Apollo Server was started implicitly and an error occurred during startup. ' + - '(Consider calling `await server.start()` immediately after ' + - '`server = new ApolloServer()` so you can handle these errors directly before ' + - 'starting your web server.) All GraphQL requests will now fail. The startup error ' + - 'was: ' + - 'load error which should be masked', - ); - expect(executor).not.toHaveBeenCalled(); + it('not calling start causes a clear error', async () => { + // Note that this test suite is not used by `apollo-server` or + // serverless frameworks, so this is legit. + expect( + createApolloServer( + { typeDefs: 'type Query{x: ID}' }, + { suppressStartCall: true }, + ), + ).rejects.toThrow('You must `await server.start()`'); }); it('uses schema over resolvers + typeDefs', async () => { @@ -894,7 +861,11 @@ export function testApolloServer( if (!this.server) { throw new Error('must listen before getting URL'); } - const { family, address, port } = (this.server.address() as AddressInfo); + const { + family, + address, + port, + } = this.server.address() as AddressInfo; if (family !== 'IPv4') { throw new Error(`The family was unexpectedly ${family}.`); @@ -1020,7 +991,8 @@ export function testApolloServer( const reports = await reportIngress.promiseOfReports; expect(reports.length).toBe(1); - const trace = Object.values(reports[0].tracesPerQuery)[0].trace![0] as Trace; + const trace = Object.values(reports[0].tracesPerQuery)[0] + .trace![0] as Trace; // There should be no error at the root, our error is a child. expect(trace.root!.error).toStrictEqual([]); @@ -1849,7 +1821,11 @@ export function testApolloServer( ); } - const { family, address, port } = (fakeUsageReportingServer.address() as AddressInfo); + const { + family, + address, + port, + } = fakeUsageReportingServer.address() as AddressInfo; if (family !== 'IPv4') { throw new Error(`The family was unexpectedly ${family}.`); } @@ -2255,7 +2231,9 @@ export function testApolloServer( ) return false; - if (requestContext.request.query!.indexOf('asyncnowrite') >= 0) { + if ( + requestContext.request.query!.indexOf('asyncnowrite') >= 0 + ) { return new Promise((resolve) => resolve(false)); } @@ -2785,7 +2763,9 @@ export function testApolloServer( }; }); - const executor = async (req: GraphQLRequestContextExecutionDidStart) => { + const executor = async ( + req: GraphQLRequestContextExecutionDidStart, + ) => { const source = req.source as string; const { startPromise, endPromise, i } = executorData[source]; startPromise.resolve(); diff --git a/packages/apollo-server-integration-testsuite/src/index.ts b/packages/apollo-server-integration-testsuite/src/index.ts index 52148630917..016cfda0c53 100644 --- a/packages/apollo-server-integration-testsuite/src/index.ts +++ b/packages/apollo-server-integration-testsuite/src/index.ts @@ -209,14 +209,22 @@ export interface CreateAppOptions { } export interface CreateAppFunc { - (options?: CreateAppOptions): ValueOrPromise; + (options?: CreateAppOptions): Promise; } export interface DestroyAppFunc { (app: any): ValueOrPromise; } -export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { +export default ({ + createApp, + destroyApp, + serverlessFramework, +}: { + createApp: CreateAppFunc; + destroyApp?: DestroyAppFunc; + serverlessFramework?: boolean; +}) => { describe('apolloServer', () => { let app: any; let didEncounterErrors: jest.MockedFunction { >>; afterEach(async () => { + // XXX nothing calls server.stop? if (app) { if (destroyApp) { await destroyApp(app); @@ -1130,78 +1139,76 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { }); }); - describe('request pipeline plugins', () => { - describe('lifecycle hooks', () => { - // This tests the backwards-compatibility behavior that ensures - // that even if you don't call server.start() yourself, the first - // GraphQL request will result in starting the server before - // serving the first request - it('calls serverWillStart before serving a request', async () => { - // We'll use this to determine the order in which - // the events we're expecting to happen actually occur and validate - // those expectations in various stages of this test. - const calls: string[] = []; - - const pluginStartedBarrier = resolvable(); - const letPluginFinishBarrier = resolvable(); - - // We want this to create the app as fast as `createApp` will allow. - // for integrations whose `applyMiddleware` currently returns a - // Promise we want them to resolve at whatever eventual pace they - // will so we can make sure that things are happening in order. - const unawaitedApp = createApp({ - graphqlOptions: { - schema, - plugins: [ - { - async serverWillStart() { - calls.push('zero'); - pluginStartedBarrier.resolve(); - await letPluginFinishBarrier; - calls.push('one'); - }, + if (serverlessFramework) { + // This tests the serverless-specific behavior that ensures that startup + // finishes before serving a request. Non-serverless frameworks don't have + // this behavior: they assert that you've done `await server.start()` + // earlier in the process. + it('calls serverWillStart before serving a request', async () => { + // We'll use this to determine the order in which + // the events we're expecting to happen actually occur and validate + // those expectations in various stages of this test. + const calls: string[] = []; + + const pluginStartedBarrier = resolvable(); + const letPluginFinishBarrier = resolvable(); + + // We want this to create the app as fast as `createApp` will allow. + // for integrations whose `applyMiddleware` currently returns a + // Promise we want them to resolve at whatever eventual pace they + // will so we can make sure that things are happening in order. + const unawaitedApp = createApp({ + graphqlOptions: { + schema, + plugins: [ + { + async serverWillStart() { + calls.push('zero'); + pluginStartedBarrier.resolve(); + await letPluginFinishBarrier; + calls.push('one'); }, - ], - }, - }); + }, + ], + }, + }); - // Account for the fact that `createApp` might return a Promise, - // and might not, depending on the integration's implementation of - // createApp. This is entirely to account for the fact that - // non-async implementations of `applyMiddleware` leverage a - // middleware as the technique for yielding to `startWillStart` - // hooks while their `async` counterparts simply `await` those same - // hooks. In a future where we make the behavior of `applyMiddleware` - // the same across all integrations, this should be changed to simply - // `await unawaitedApp`. - app = 'then' in unawaitedApp ? await unawaitedApp : unawaitedApp; - - // Intentionally fire off the request asynchronously, without await. - const res = request(app) - .get('/graphql') - .query({ - query: 'query test{ testString }', - }) - .then(res => { - calls.push('two'); - return res; - }); + // Account for the fact that `createApp` might return a Promise, + // and might not, depending on the integration's implementation of + // createApp. This is entirely to account for the fact that + // non-async implementations of `applyMiddleware` leverage a + // middleware as the technique for yielding to `startWillStart` + // hooks while their `async` counterparts simply `await` those same + // hooks. In a future where we make the behavior of `applyMiddleware` + // the same across all integrations, this should be changed to simply + // `await unawaitedApp`. + app = 'then' in unawaitedApp ? await unawaitedApp : unawaitedApp; - // At this point calls might be [] or ['zero'] because the back-compat - // code kicks off start() asynchronously. We can safely wait on - // the plugin's serverWillStart to begin. - await pluginStartedBarrier; - expect(calls).toEqual(['zero']); - letPluginFinishBarrier.resolve(); + // Intentionally fire off the request asynchronously, without await. + const res = request(app) + .get('/graphql') + .query({ + query: 'query test{ testString }', + }) + .then((res) => { + calls.push('two'); + return res; + }); - // Now, wait for the request to finish. - await res; + // At this point calls might be [] or ['zero'] because the back-compat + // code kicks off start() asynchronously. We can safely wait on + // the plugin's serverWillStart to begin. + await pluginStartedBarrier; + expect(calls).toEqual(['zero']); + letPluginFinishBarrier.resolve(); - // Finally, ensure that the order we expected was achieved. - expect(calls).toEqual(['zero', 'one', 'two']); - }); + // Now, wait for the request to finish. + await res; + + // Finally, ensure that the order we expected was achieved. + expect(calls).toEqual(['zero', 'one', 'two']); }); - }); + } describe('Persisted Queries', () => { const query = '{testString}'; diff --git a/packages/apollo-server-koa/src/ApolloServer.ts b/packages/apollo-server-koa/src/ApolloServer.ts index e02aaa8d46e..eff6f43e8eb 100644 --- a/packages/apollo-server-koa/src/ApolloServer.ts +++ b/packages/apollo-server-koa/src/ApolloServer.ts @@ -49,6 +49,9 @@ export class ApolloServer extends ApolloServerBase { } public applyMiddleware({ app, ...rest }: ServerRegistration) { + // getMiddleware calls this too, but we want the right method name in the error + this.assertStarted('applyMiddleware'); + app.use(this.getMiddleware(rest)); } @@ -65,10 +68,7 @@ export class ApolloServer extends ApolloServerBase { }: GetMiddlewareOptions = {}): Middleware { if (!path) path = '/graphql'; - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('getMiddleware'); const middlewares = []; diff --git a/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts index 3cd468353c9..5d73db9b155 100644 --- a/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-koa/src/__tests__/ApolloServer.test.ts @@ -28,9 +28,9 @@ describe('apollo-server-koa', () => { let server: any; let httpServer: http.Server; testApolloServer( - async (options: any, suppressStartCall?: boolean) => { - server = new ApolloServer(options); - if (!suppressStartCall) { + async (config: any, options) => { + server = new ApolloServer(config); + if (!options?.suppressStartCall) { await server.start(); } const app = new Koa(); @@ -59,6 +59,7 @@ describe('apollo-server-koa', () => { options: Partial = {}, ) { server = new ApolloServer({ stopOnTerminationSignals: false, ...serverOptions }); + await server.start(); app = new Koa(); server.applyMiddleware({ ...options, app }); diff --git a/packages/apollo-server-koa/src/__tests__/datasource.test.ts b/packages/apollo-server-koa/src/__tests__/datasource.test.ts index 5c80b42cf12..507305a48aa 100644 --- a/packages/apollo-server-koa/src/__tests__/datasource.test.ts +++ b/packages/apollo-server-koa/src/__tests__/datasource.test.ts @@ -105,6 +105,7 @@ describe('apollo-server-koa', () => { }, }), }); + await server.start(); const app = new Koa(); server.applyMiddleware({ app }); @@ -137,6 +138,7 @@ describe('apollo-server-koa', () => { }, }), }); + await server.start(); const app = new Koa(); server.applyMiddleware({ app }); diff --git a/packages/apollo-server-koa/src/__tests__/koaApollo.test.ts b/packages/apollo-server-koa/src/__tests__/koaApollo.test.ts index 5abf182517b..d42e89b27b2 100644 --- a/packages/apollo-server-koa/src/__tests__/koaApollo.test.ts +++ b/packages/apollo-server-koa/src/__tests__/koaApollo.test.ts @@ -4,7 +4,7 @@ import testSuite, { } from 'apollo-server-integration-testsuite'; import { Config } from 'apollo-server-core'; -function createApp(options: CreateAppOptions = {}) { +async function createApp(options: CreateAppOptions = {}) { const Koa = require('koa'); const { ApolloServer } = require('../ApolloServer'); const app = new Koa(); @@ -12,6 +12,7 @@ function createApp(options: CreateAppOptions = {}) { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); + await server.start(); server.applyMiddleware({ app }); return app.listen(); } @@ -33,5 +34,5 @@ describe('koaApollo', () => { }); describe('integration:Koa', () => { - testSuite(createApp, destroyApp); + testSuite({createApp, destroyApp}); }); diff --git a/packages/apollo-server-lambda/src/__tests__/lambdaApollo.test.ts b/packages/apollo-server-lambda/src/__tests__/lambdaApollo.test.ts index b51275fe7b8..82e72ee3a2f 100644 --- a/packages/apollo-server-lambda/src/__tests__/lambdaApollo.test.ts +++ b/packages/apollo-server-lambda/src/__tests__/lambdaApollo.test.ts @@ -6,7 +6,7 @@ import testSuite, { import { Config } from 'apollo-server-core'; import {createMockServer} from './mockServer'; -const createLambda = (options: CreateAppOptions = {}) => { +const createLambda = async (options: CreateAppOptions = {}) => { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); @@ -17,5 +17,5 @@ const createLambda = (options: CreateAppOptions = {}) => { } describe('integration:Lambda', () => { - testSuite(createLambda); + testSuite({createApp: createLambda, serverlessFramework: true}); }); diff --git a/packages/apollo-server-micro/src/ApolloServer.ts b/packages/apollo-server-micro/src/ApolloServer.ts index 4909c3b25cd..62064d48c00 100644 --- a/packages/apollo-server-micro/src/ApolloServer.ts +++ b/packages/apollo-server-micro/src/ApolloServer.ts @@ -29,10 +29,7 @@ export class ApolloServer extends ApolloServerBase { disableHealthCheck, onHealthCheck, }: ServerRegistration = {}) { - // In case the user didn't bother to call and await the `start` method, we - // kick it off in the background (with any errors getting logged - // and also rethrown from graphQLServerOptions during later requests). - this.ensureStarting(); + this.assertStarted('createHandler'); return async (req: MicroRequest, res: ServerResponse) => { this.graphqlPath = path || '/graphql'; diff --git a/packages/apollo-server-micro/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-micro/src/__tests__/ApolloServer.test.ts index b6b6f7a1f37..3e17130fc09 100644 --- a/packages/apollo-server-micro/src/__tests__/ApolloServer.test.ts +++ b/packages/apollo-server-micro/src/__tests__/ApolloServer.test.ts @@ -24,6 +24,7 @@ async function createServer(options: object = {}): Promise { resolvers, stopOnTerminationSignals: false, }); + await apolloServer.start(); const service = micro(apolloServer.createHandler(options)); const uri = await listen(service); return { diff --git a/packages/apollo-server-micro/src/__tests__/microApollo.test.ts b/packages/apollo-server-micro/src/__tests__/microApollo.test.ts index 4c0a5b4d87e..5442b36ee08 100644 --- a/packages/apollo-server-micro/src/__tests__/microApollo.test.ts +++ b/packages/apollo-server-micro/src/__tests__/microApollo.test.ts @@ -7,10 +7,11 @@ import { Config } from 'apollo-server-core'; import { ApolloServer } from '../ApolloServer'; -function createApp(options: CreateAppOptions = {}) { +async function createApp(options: CreateAppOptions = {}) { const server = new ApolloServer( (options.graphqlOptions as Config) || { schema: Schema }, ); + await server.start(); return micro(server.createHandler()); } @@ -23,5 +24,5 @@ describe('microApollo', function() { }); describe('integration:Micro', function() { - testSuite(createApp); + testSuite({createApp}); }); diff --git a/packages/apollo-server-testing/src/__tests__/createTestClient.test.ts b/packages/apollo-server-testing/src/__tests__/createTestClient.test.ts index b345da5ac9e..f929e6c1a9e 100644 --- a/packages/apollo-server-testing/src/__tests__/createTestClient.test.ts +++ b/packages/apollo-server-testing/src/__tests__/createTestClient.test.ts @@ -32,6 +32,10 @@ describe('createTestClient', () => { resolvers, }); + beforeAll(async () => { + await myTestServer.start(); + }); + it('allows queries', async () => { const query = `{ test(echo: "foo") }`; const client = createTestClient(myTestServer);