Bartosz Raubo
Bartosz Raubo

Reputation: 106

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.

Upvotes: 1

Views: 647

Answers (2)

Kurnia Hayati
Kurnia Hayati

Reputation: 11

For unit tests, using the approach I mentioned in the other thread you don't need anything from typeorm, it just works. That's why I suggested it. With minimal code in your tests It works today and works great.

For integration tests you probably do actually want a real database of some sort. Either a separate database/schema in your existing local dev database or a temporary sqlite database. If typeorm doesn't already then supporting some form of purely in-memory database may be a good thing here as well.

Expanding on my last comment, testdouble does mocking really well. sinon struggles in lots of ways. In particular, testdoubles ability to use ES2015 Proxy objects as mocks is really awesome. but testdouble only does mocking, there's lots of other utilities in sinon that are really awesome. it's just the mocking tools that are kind of lack-lustre.

Upvotes: 0

Bartosz Raubo
Bartosz Raubo

Reputation: 106

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();
  });

Upvotes: 1

Related Questions