Nikita
Nikita

Reputation: 73

Do not pass e2e tests in framework NestJS

I use the NestJS framework. When using @nestjs/typeorm, I create a repository with users. Using this approach to creating a repository, my e2e tests. When working with a database, all data is successfully saved. There are no problems with the connection. Here are my files:

app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
import { AuthModule } from './modules/auth/auth.module';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
    AuthModule,
  ],
})
export class AppModule {
  constructor(private readonly connection: Connection) { }
}

auth.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { Users } from '../../entity/Users';

@Module({
  imports: [TypeOrmModule.forFeature([Users])],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

auth.service.ts

...
      // my repo
      constructor(
        @InjectRepository(Users)
        private readonly usersRepository: Repository<Users>,
      ) { }
...

app.e2e-spec.ts

import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

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

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(404)
      .expect('{"statusCode":404,"error":"Not Found","message":"Cannot GET /"}'); //todo fix me
  });
});

Everything is written in accordance with the documentation. When you run npm run test:e2e, the console gives the following error:

> [email protected] test:e2e 
> jest --config ./test/jest-e2e.json

[Nest] 7206   - 2/2/2019, 5:06:52 PM   [TypeOrmModule] Unable to connect to the database. Retrying (1)...
Error: getaddrinfo ENOTFOUND postgres postgres:5432
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:57:26)
[Nest] 7206   - 2/2/2019, 5:06:55 PM   [TypeOrmModule] Unable to connect to the database. Retrying (2)... +3234ms
Error: getaddrinfo ENOTFOUND postgres postgres:5432
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:57:26)
 FAIL  test/app.e2e-spec.ts (6.198s)
  AppController (e2e)
    ✕ / (GET) (6ms)

  ● AppController (e2e) › / (GET)

    Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

      at mapper (../node_modules/jest-jasmine2/build/queue_runner.js:41:52)

  ● AppController (e2e) › / (GET)

    TypeError: Cannot read property 'getHttpServer' of undefined

      17 |
      18 |   it('/ (GET)', () => {
    > 19 |     return request(app.getHttpServer())
         |                        ^
      20 |       .get('/')
      21 |       .expect(404)
      22 |       .expect('{"statusCode":404,"error":"Not Found","message":"Cannot GET /"}'); // todo fix me

      at Object.<anonymous> (app.e2e-spec.ts:19:24)

Please, help me!

Upvotes: 6

Views: 13551

Answers (4)

If you want to write e2e tests with mocks you don't need to import the AppModule you only should import your AppController and AppService, in this way you avoid to connect to your database and use mocks to test the entire application flow.

import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppController } from './../src/app.controller';
import { AppService } from './../src/app.service';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [],
      controllers: [AppController],
      providers: [AppService],
    }).compile();

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(404)
      .expect('{"statusCode":404,"error":"Not Found","message":"Cannot GET /"}'); //todo fix me
  });
});

With this approach you get a clean testing module without TypeOrmModule. NOTE: if you need to mock the service, the Test has a method overrideProvider to override your service and mothods like useClass, useValue or useFactory to provide your mock.

If you want to write an integration test to confirm that all together works fine you can override the configuration of your TypeOrmModule, passing it to the testing module with a new DB configuration like this post describes.

I hope I've helped. Good luck and regards.

Upvotes: 12

Binh Ho
Binh Ho

Reputation: 4936

Never uses TypeOrmModule in unit test. It will be make connect to DB. When your DB not started you will can not run unit test.

Try this example.

// mytest.e2e-spec.ts
import * as request from 'supertest';
import { Test } from "@nestjs/testing";
import { INestApplication } from '@nestjs/common';
import { MyTestsController } from './myTests.controller';
import { MyTestsService } from ".";
import { Warehouse } from './myTest.entity';
import { getRepositoryToken } from '@nestjs/typeorm';

describe("MyTestsController (e2e)", () => {

  let app: INestApplication;
  const myTests = [
    {
      id: "1ccc2222a-8072-4ff0-b5ff-103cc85f3be6",
      name: "Name #1",
    }
  ];

  const myTestsCount = 1;
  const getAllResult = { myTests, myTestsCount };
  // Mock data for service
  let myTestsService = { getAll: () => getAllResult };

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      providers: [
        MyTestsService,
        {
          provide: getRepositoryToken(Warehouse),
          useValue: myTestsService
        }
      ],
      controllers: [MyTestsController],
    })
      .overrideProvider(MyTestsService)
      .useValue(myTestsService)
      .compile();

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

  beforeEach(async () => {});

  it(`/GET all myTests`, async() => {
    return await request(app.getHttpServer())
      .get('/myTests')
      .expect(200)
      .expect(myTestsService.getAll());
  });

  afterAll(async () => {
    await app.close();
  });

});

And the service

// myTests.service.ts
public async getAll(query?): Promise<myTestsRO> {
  const qb = await this.repo.createQueryBuilder("myTests");
  const myTestsCount = await qb.getCount();

  if ("limit" in query) {
    qb.limit(query.limit);
  }

  if ("offset" in query) {
    qb.offset(query.offset);
  }

  const myTests = await qb
    .getMany()
    .then(myTests =>
      myTests.map(entity => WarehouseDto.fromEntity(entity))
    );

  return { myTests, myTestsCount };
}

And controller

// myTest.controller.ts
@Get()
public async getAll(@Query() query): Promise<myTestsRO> {
  try {
    return await this.myTestsService.getAll(query);
  } catch (error) {
    throw new InternalServerErrorException(error.message);
  }
}

Hope this help!

Upvotes: 0

Subbre
Subbre

Reputation: 73

That error can occur even if you typed your api paths wrong. It doesnt log the error but it always throws on that line you shown. I also had a similar problem, i was putting globalPrefix to /api and in my tests i forgot that its another nest application instance, so removing /api/ from e2e mocks fixed everything.

Upvotes: 0

Tom Nurkkala
Tom Nurkkala

Reputation: 652

Be sure to close the app object with app.close() per the example at https://docs.nestjs.com/fundamentals/testing#end-to-end-testing.

Upvotes: 1

Related Questions