Reputation: 106
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'
}
}
}
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();
});
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.
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
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
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