I am trying to setup automated tests for TypeGraphQL using Jest+SuperTest. I am seeing an error where args
and ctx
passed into a query are null. When this happens, I get a Cannot read property 'date' of undefined
error.
This only happens during automated testing, not when I hit the resolver from graphQL playground. It also works fine on the login mutation I send first, but crashes on the getAll query.
When debugging, I found that the same test is ran twice, which is very strange to me. It seemingly passes on the first time and fails on the second. Jest reports 1 test failed and 1 passed, but there is only this one test being ran. It seems to fail on the first run and pass on the second, but I think I've seen it work the other way around as well.
Test Suites: 1 failed, 2 skipped, 1 passed, 2 of 4 total
Tests: 1 failed, 6 skipped, 1 passed, 8 total
test setup
beforeAll(async () => {
request = supertest(http.createServer(await createMyAppServer()));
sendReq = reqSender(request);
});
afterAll(async (done) => {
//nothing
})
//test
test("get All with login", async (done: any) => {
let loginResp;
loginResp = await sendReq(TEST_VALID_LOGIN_GQL);
let firstCookie = loginResp.cookie;
let getAllResp = await sendReq(`query {
getAllModelName {
id
}
}`,
firstCookie,200,true);
expect(getAllResp.text.data[`getAllModelName`].length).toBeGreaterThan(0);
}
done();
});
//request sender (for reference, can ignore)
export function reqSender(requestServer:any){
return async function sendReq(
gql: string,
prevCookie: any = [],
expectedStatus = 200,
logErr = false
) {
let fullResp: any;
let cookie: any;
gql = gql.replace("\n","");
try {
let resp = await requestServer
.post("/graphql")
.send({
query: gql,
})
.set("Accept", "application/json")
.set("cookie", prevCookie)
.set("Content-Type", "application/json")
.then((resolver: any, rejector: any) => {
if (rejector) throw rejector;
fullResp = resolver.res as any;
cookie = fullResp.headers["set-cookie"];
try{
expect(fullResp.headers['content-type'].includes("application/json")).toEqual(true);
expect(fullResp.statusCode).toEqual(expectedStatus)
}catch(e){
console.error(fullResp.text);
throw e;
}
});
} catch (e) {
console.error("request= " + gql);
console.error(e);
throw e;
}
let errs = JSON.parse(fullResp.text).errors;
if(errs && logErr) console.error(errs);
return { cookie, fullResp, text: JSON.parse(fullResp.text) };
};
}
resolver parent
@ArgsType()
export class CommonArgs {
@Field({ nullable: true })
date: Date;
}
export function createBaseResolver<T extends UserCreatedEntity>(suffix: string, objectTypeCls: T) {
@Query(type => [objectTypeCls], { name: `getAll${suffix}` })
async getByDateQuery(@Args() args: CommonArgs, @Ctx() ctx: any): Promise<T> {//ISSUE HERE :: args and ctx are null
let where = {} as any;
if (args.date) where.date = (Between(startOfDay(args.date).toISOString(), endOfDay(args.date).toISOString()) as any); //ERROR here, if I remove this then ctx will throw an error when I try to use vars on it.
return this.getAll(where, ctx);
}
async getAll(whereArgs: any, ctx: any): Promise<T> {
this.checkForLogin(ctx);
let beCastedObj = (<typeof UserCreatedEntity>(objectTypeCls as any));
let findArgs = {} as any;
findArgs.where = whereArgs || {};
findArgs.where.userCreator = { id: ctx.req.session.userId };
let entities =[];
entities = await beCastedObj.find(findArgs) as any;
entities.forEach((e: any) => {
this.checkCanView(ctx, e);
});
return entities;
}
}
resolver
@Resolver(of => ModelName)
export class ModelNameResolver extends BaseResolver {
}
running jest via npm scripts:
"startServer": "NODE_PATH=./src DEBUG=express:* NODE_ENV=dev nodemon -w src --ext ts --exec node --inspect -r tsconfig-paths/register -r ts-node/register src/index.ts",
"testDebug": "NODE_ENV=test npx --debug-brk=5858 jest --runInBand --testTimeout=10000",
"test": "NODE_ENV=test jest --runInBand --testTimeout=10000"
jest.config.ts
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' } ),
testNamePattern: 'get All.*',
};
When I finally searched on Google instead of DuckDuckGo, I found lots of people having this same problem when using TS. None of those solutions worked for me though, it may just be an active bug: https://github.com/kulshekhar/ts-jest/issues/1342
It took me very little time to switch to Mocha+Chai, which now works flawlessly. Wish I had done it sooner, but I'm glad it is working now.
Edit:
Eventually Mocha ran into the same issue, where some requests failed and others had the args+ctx coming in as null. To solve this I eventually moved all the tests into the same test file. Something is wrong with my test teardown/setup, but I can't figure out what it is. For now, I'm leaving them all in the same file.