NestJS - GraphQL 在我开玩笑的 E2E 中不起作用 "is not a function"

NestJS - GraphQL not working in my jest E2E "is not a function"

我已经用 NestJS + Apollo / Express 在我的 GraphQL 应用程序上解决 E2E 测试问题 3 天了。

当我 运行 我的应用程序使用 无服务器离线 或直接使用我的 主文件 时,它运行完美。我有我的图表和我需要的所有东西:)

但是,当我 运行 使用 Jest 进行 E2E 测试时,我从 beforeEach 中的 await app.init() 收到错误。

使用 package.json 和依赖项后,抛出的错误是 TypeError: (0 , schema_1.makeExecutableSchema) is not a function

有人知道吗?我完全被封锁了......:(

 FAIL  src/__tests/graphql/common.e2e-spec.ts
  GraphQL - CommonModule (e2e)
    Query - Test
      ✕ should return the test query with typename TestSuccess (code: 200) (385 ms)

  ● GraphQL - CommonModule (e2e) › Query - Test › should return the test query with typename TestSuccess (code: 200)

    TypeError: (0 , schema_1.makeExecutableSchema) is not a function

      15 |     app = moduleFixture.createNestApplication();
      16 |     app.useGlobalPipes(new ValidationPipe());
    > 17 |     await app.init();
         |     ^
      18 |
      19 |     request = superRequest(app.getHttpServer());
      20 |   });

      at GraphQLFactory.mergeWithSchema (node_modules/@nestjs/graphql/dist/graphql.factory.js:30:72)
      at ApolloDriver.start (node_modules/@nestjs/apollo/dist/drivers/apollo.driver.js:19:25)
      at GraphQLModule.onModuleInit (node_modules/@nestjs/graphql/dist/graphql.module.js:103:9)
      at Object.callModuleInitHook (node_modules/@nestjs/core/hooks/on-module-init.hook.js:51:9)
      at Proxy.callInitHook (node_modules/@nestjs/core/nest-application-context.js:179:13)
      at Proxy.init (node_modules/@nestjs/core/nest-application.js:96:9)
      at Object.<anonymous> (src/__tests/graphql/common.e2e-spec.ts:17:5)

package.json

  "dependencies": {
    "@nestjs/apollo": "^10.0.4",
    "@nestjs/common": "^8.3.1",
    "@nestjs/core": "^8.3.1",
    "@nestjs/graphql": "^10.0.4",
    "@nestjs/jwt": "^8.0.0",
    "@nestjs/passport": "^8.2.1",
    "@nestjs/platform-express": "^8.3.1",
    "@prisma/client": "^3.9.2",
    "@vendia/serverless-express": "^4.5.3",
    "apollo-server-core": "^3.6.3",
    "apollo-server-express": "^3.6.3",
    "apollo-server-plugin-base": "^3.5.1",
    "aws-lambda": "^1.0.7",
    "bcryptjs": "^2.4.3",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.13.2",
    "dotenv": "^16.0.0",
    "env-var": "^7.1.1",
    "express": "^4.17.1",
    "graphql": "^16.3.0",
    "graphql-query-complexity": "^0.11.0",
    "nestjs-pino": "^2.5.0",
    "passport": "^0.5.2",
    "passport-jwt": "^4.0.0",
    "pg": "^8.7.3",
    "pino-http": "^6.6.0",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.5.3"
  },
  "devDependencies": {
    "@faker-js/faker": "^6.0.0-alpha.5",
    "@nestjs/cli": "^8.2.0",
    "@nestjs/schematics": "^8.0.0",
    "@nestjs/testing": "^8.0.0",
    "@serverless/typescript": "^3.2.0",
    "@types/aws-lambda": "^8.10.92",
    "@types/bcryptjs": "^2.4.2",
    "@types/express": "^4.17.13",
    "@types/jest": "27.4.0",
    "@types/node": "^16.0.0",
    "@types/passport-local": "^1.0.34",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "apollo-server-testing": "^2.25.3",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "^27.5.1",
    "npm-run-all": "^4.1.5",
    "prettier": "^2.5.1",
    "prisma": "^3.9.2",
    "prisma-nestjs-graphql": "^14.6.1",
    "serverless": "^3.2.1",
    "serverless-offline": "^8.4.0",
    "serverless-plugin-optimize": "^4.2.1-rc.1",
    "serverless-plugin-warmup": "^7.0.2",
    "source-map-support": "^0.5.21",
    "supertest": "^6.2.2",
    "ts-jest": "^27.1.3",
    "ts-loader": "^9.2.3",
    "ts-morph": "^13.0.3",
    "ts-node": "^10.5.0",
    "tsc-alias": "^1.5.0",
    "tsconfig-paths": "^3.12.0",
    "typescript": "^4.5.5",
    "webpack": "^5.0.0"
  }

jest.config.ts

module.exports = {
  moduleFileExtensions: ['js', 'json', 'ts'],
  testEnvironment: 'node',
  transform: {
    '^.+\.(t|j)s$': 'ts-jest',
  },
  testRegex: '.(spec|e2e-spec).ts$',
  moduleNameMapper: {
    '^@features/(.*)': '<rootDir>/src/features/',
    '^@tests/(.*)': '<rootDir>/src/__tests/',
    '^@graphql': '<rootDir>/src/@graphql/generated',
    '^@types': '<rootDir>/src/@types',
    '^@utils': '<rootDir>/src/utils',
    '^@config': '<rootDir>/src/config',
  },
  testPathIgnorePatterns: [
    '<rootDir>/dist/',
    '<rootDir>/prisma/',
    '<rootDir>/bin/',
    '<rootDir>/node_modules/',
    '<rootDir>/.github/',
  ],
};

graphql.e2e-spec.ts

import { GraphQLModule } from '@features/graphql/graphql.module';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import superRequest, { SuperTest, Test as TestItem } from 'supertest';

describe('GraphQL - CommonModule (e2e)', () => {
  let app: INestApplication;
  let request: SuperTest<TestItem>;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [GraphQLModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe());
    await app.init();

    request = superRequest(app.getHttpServer());
  });

  describe('Query - Test', () => {
    it('should return the test query with typename TestSuccess (code: 200)', async () => {
      return request
        .post('/graphql')
        .send({
          query: `
            query {
              test {
                __typename
              }
            }`,
        })
        .then((res) => {
          expect(res.status).toBe(200);
          expect(res.body.data.test.__typename).toBe('TestSuccess');
        });
    });
  });
});

graphql.module.ts

import { config } from '@config';
import { AppService } from '@features/app.service';
import { GraphQLAuthModule } from '@features/graphql/auth/auth.module';
import { CommonModule } from '@features/graphql/common/common.module';
import { UserModule } from '@features/graphql/user/user.module';
import { GraphQLComplexityPlugin } from '@features/graphql/_plugins';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule as NESTJSGraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
    GraphQLAuthModule,
    NESTJSGraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/@graphql/schema.gql'),
      sortSchema: true,
      debug: config.graphql.isDebugEnabled,
      introspection: true,
      playground: config.graphql.isPlaygroundEnabled
        ? {
            settings: { 'schema.polling.enable': false },
          }
        : false,
    }),
    UserModule,
    CommonModule,
  ],
  providers: [AppService, GraphQLComplexityPlugin],
})
export class GraphQLModule {
  constructor(private readonly appService: AppService) {
    if (!this.appService.checkEnv()) process.exit();
  }
}

经过数小时的紧张编程... 终于找到问题了

如果您对导入使用某些“别名”,请小心,因为它可能会覆盖某些已使用的包。

在这里,我使用了 @graphql 别名,它破坏了我所有的测试。 当我删除它时,问题就消失了。

  moduleNameMapper: {
    '^@features/(.*)': '<rootDir>/src/features/',
    '^@tests/(.*)': '<rootDir>/__tests/',
    '^@utils': '<rootDir>/src/utils',
    '^@config': '<rootDir>/src/config',
    '^@types': '<rootDir>/src/@types',
    '^@graphql': '<rootDir>/src/@graphql/generated', // to remove
  },

  moduleNameMapper: {
    '^@features/(.*)': '<rootDir>/src/features/',
    '^@tests/(.*)': '<rootDir>/__tests/',
    '^@utils': '<rootDir>/src/utils',
    '^@config': '<rootDir>/src/config',
    '^@types': '<rootDir>/src/@types',
  },