I write tests uning Jest + react testing library + jsdom 15. In one of the tests I use ApolloServer - I start it beforeAll and stop it after all.
When I used apollo-server version 2.19.0 everything worked as expected. When upgrading to apollo-server version 2.21.0,
server.stop() hangs forever. I try using stopGracePeriodMillis and that doesn't work as well.
[ apollo-server version 2.21.0 ] The package name and version of Apollo showing the problem.
import React from 'react';
import { gql } from '@apollo/client';
import { ApolloServer } from 'apollo-server';
// Just a random schema
const typeDefs = gql`
type Query {
books: [Book]!
}
type Book {
title: String
author: Author
}
type Author {
name: String
books: [Book]
}
`;
let mockServer;
beforeAll(async () => {
mockServer = new ApolloServer({typeDefs});
try {
const { url } = await mockServer.listen({
port: 0
});
console.log(`馃殌 Server ready at ${url}`);
return url;
} catch (error) {
console.error('Error running server', error);
throw error;
}
});
afterAll(async () => {
await mockServer?.stop();
console.log('stopped server'); // This line is never reached
});
describe('VesselName', () => {
it('Does nothing', async () => {
});
});
My guess is that this change broke the stop functionality, maybe because the code runs from jsdom browser + jest, and stoppable might have issues with it.
Update:
I think my guess is correct, I found this in the jsdom browser console:
console.error node_modules/jest-jasmine2/build/jasmine/Env.js:290
TypeError: setTimeout(...).unref is not a function
at Immediate.<anonymous> (/Users/nogashimoni/Documents/projects/javascript/marint-ui/node_modules/stoppable/lib/stoppable.js:43:39)
at processImmediate (internal/timers.js:461:21)
Thank you!
When I run the code not from jest, everything works fine and the server stops, so I assume it's related to the fact that I run this from Jest + react testing library, and that the code runs from jsdom .
(This works perfectly fine:
const React = require('react')
const { gql } = require( '@apollo/client')
const { ApolloServer } = require('apollo-server');
// Just a random schema
const typeDefs = gql`
type Query {
books: [Book]!
}
type Book {
title: String
author: Author
}
type Author {
name: String
books: [Book]
}
`;
async function demo() {
mockServer = new ApolloServer({typeDefs});
try {
const { url } = await mockServer.listen({
port: 0
});
console.log(`馃殌 Server ready at ${url}`);
} catch (error) {
console.error('Error running server', error);
throw error;
}
await mockServer?.stop();
console.log('stopped server'); // This line is reached when not running from Jest
}
demo();
)
Update - it seems that the stoppable code can't run from jsdom.
As a temp solution, I added a polyfill for setTimeout(...).unref, like so:
const setTimeoutProto = window.setTimeout().__proto__;
if (!setTimeoutProto.unref) {
setTimeoutProto.unref = function () {};
}
It's not a good solution IMO, but it makes server.stop() work and that's good enough for me for now.
Hi, I appreciate the test file but since it sounds like this issue is very specific to the particular way you run jest with jsdom, is it possible to give a bit more context on how to run it? eg maybe as a git repo I can clone, or something like codesandbox.io?
If the issue is unref, then one workaround would be to pass stopGracePeriodMillis: Infinity to new ApolloServer. This means that the new stoppable functionality will still kill idle HTTP connections but just won't kill ones that are actively running an HTTP request. For a test, this actually seems like a reasonable approach since your tests probably won't end with active HTTP requests (and if they do, Jest's timeouts will kick in?).
I'm definitely also open to forking stoppable (either into @apollographql/stoppable or just copying the single file into this repo). I think we could replace the use of unref by calling clearTimeout from the server.close callback.
Let's see if upstream takes https://github.com/hunterloftis/stoppable/pull/33
In the meantime, let me know if stopGracePeriodMillis: Infinity is a reasonable workaround.
Most helpful comment
Update - it seems that the stoppable code can't run from jsdom.
As a temp solution, I added a polyfill for setTimeout(...).unref, like so:
const setTimeoutProto = window.setTimeout().__proto__; if (!setTimeoutProto.unref) { setTimeoutProto.unref = function () {}; }It's not a good solution IMO, but it makes server.stop() work and that's good enough for me for now.