Search code examples
graphqlnestjstypeorme2e-testingsupertest

TypeORM Entity Validators not working when E2E testing GraphQL API with SuperTest


Morning everybody!

Tech Stack: NestJS + GraphQL + TypeORM

Have a strange problem with TypeORM Entity Field validators (@IsURL() & @IsNotEmpty()) not working when running E2E tests using SuperTest with a GraphQL API.

Important to note that the application functions as expected locally & in GraphQL Playground, with the validators triggering.

Take URL validation for example:

Response from Playground with correct URL:

{
  "data": {
    "createProject": {
      "uuid": "da1d8f48-2109-4629-a10f-67a5a7c3a5a2",
      "title": "f",
      "description": "descriptions",
      "releaseDate": null,
      "websiteURL": "www.f.com",
      "slug": "f"
    }
  }
}

Bad URL:

{
  "errors": [
    {
      "message": "Bad Request Exception",
      "extensions": {
        "code": "BAD_USER_INPUT",
        "response": {
          "statusCode": 400,
          "message": [
            "websiteURL must be an URL address"
          ],
          "error": "Bad Request"
        }
      }
    }
  ],
  "data": null
}

But when an invalid URL is used during an e2e test, no error is raised - seems that the validators are not triggering.

{
    data: {
          createProject: {
            uuid: '2be26d1f-d6b9-4a82-addc-778e497cdb1e',
            title: 'URL Project',
            description: 'Sit aut iusto dolor sunt.',
            releaseDate: null,
            websiteURL: 'invalid url',
            slug: 'url-project'
          }
        }
      }

Relevant Code

Test Setup

beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        TypeOrmModule.forRoot({
          ...testDataSourceOptions,
          entities: [],
          autoLoadEntities: true,
          dropSchema: true,
        }),
        AppModule,
      ],
    }).compile();

    const dataSource = new DataSource(testDataSourceOptions);
    await dataSource.initialize();
    await dataSource.runMigrations();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

Test

it('does not allow invalid urls', () => {
        const newURLProject = (title: string, websiteURL: string) => {
          const description = faker.lorem.sentence();
          const query = `
                    mutation CreateProject {
                        createProject(newProject: {
                            title: "${title}",
                            description: "${description}",
                            websiteURL: "${websiteURL}"
                        }) {
                            uuid
                            title
                            description
                            releaseDate
                            websiteURL
                            slug
                        }
                    }
                `;
          return { title, description, websiteURL, query };
        };

        const projectURL = newURLProject("URL Project", "invalid url")
        console.log(projectURL.websiteURL)

        return request(app.getHttpServer())
          .post('/graphql')
          .send({ query: projectURL.query })
          .expect(200)
          .expect(
            ({
              body: {
                errors: { errors }
              } 
            }) => {
              console.log(errors)
              expect(errors[0].extensions.response.statusCode).toEqual(400);
              expect(errors[0].extensions.response.message).toEqual(["websiteURL must be an URL address"]);
            }
          )
      })
    });

I can include the relevant sections of the DTO and Entity code - but these work fine within the Playground and through curl so I do not think they are the cause of the problem.

Attempted solutions

I have rewritten the test code to use supertest-graphql, to no effect. Next step is to try replacing Supertest entirely, but that will be a significant task so I wanted to seek that Stack Overflow wisdom first.

Any help/advice is much appreciated.


Solution

  • We managed to find the solution in this PR - Class Validation had to be passed manually to the e2e test file by adding app.useGlobalPipes(new ValidationPipe());

    Fixed test setup below:

    beforeEach(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
          imports: [
            TypeOrmModule.forRoot({
              ...testDataSourceOptions,
              entities: [],
              autoLoadEntities: true,
              dropSchema: true,
            }),
            AppModule,
          ],
        }).compile();
    
        const dataSource = new DataSource(testDataSourceOptions);
        await dataSource.initialize();
        await dataSource.runMigrations();
    
        app = moduleFixture.createNestApplication();
        app.useGlobalPipes(new ValidationPipe()); // <----- here
        await app.init();
      });