Search code examples
javascriptjestjsgraphqlsupertesttypegraphql

Jest running the same test multiple times, but crashes on subsequent runs


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.*',
};

Solution

  • 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.