DEV Community

Alfi Samudro Mulyo
Alfi Samudro Mulyo

Posted on

Build Complete REST API Feature with Nest JS (Using Prisma and Postgresql) from Scratch - Beginner-friendly - PART 6

Scope of discussion:

  1. Explain about e2e (end-to-end) testing
  2. Handle multiple env files
  3. Write e2e (end-to-end) testing code

In the previous part, we've written code for unit testing. Now, let's move to e2e testing.

What is e2e testing? end-to-end (e2e) testing is a type of software testing that is used to validate the entire system or application from start to finish. E2E testing is designed to simulate real-world scenarios and test the system's functionality, performance, and reliability in a production-like environment.

So, we will hit our real API which will make some updates to the database as well. That's the reason why we need to create .env.test and set a different DATABASE_URL in it, especially for e2e testing. We will use a different database. If we don't do that, our production or development database might be broken.

Pre-configuration

Since we're going to use another env file called .env.test, we need to rename our .env file to something else, for example, I renamed my .env to .env.development. Why? because .env will overwrite other env files. I've tried that before. My .env.test won't work unless I rename my file .env. This will also be useful, we can set any other environment like:

  • .env.production
  • .env.development
  • .env.test based on our needs. Normally, each environment will have a different configuration.

After that, we need to update our scripts inside package.json by adding NODE_ENV as below:

"start": "NODE_ENV=production nest start",
"start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "NODE_ENV=development nest start --debug --watch",
"start:prod": "NODE_ENV=production node dist/main",
...
"test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json"
Enter fullscreen mode Exit fullscreen mode

e2e testing configurations

Unlike unit testing, our e2e testing code is written in a separate folder /test. Inside /test folder, we'll find two files; app.e2e-spec.ts and jest-e2e.json. Before diving into the code, let's update jest-e2e.json by adding moduleNameMapper property:

"moduleNameMapper": {
  "^src/(.*)": "<rootDir>/../src/$1"
}
Enter fullscreen mode Exit fullscreen mode

otherwise, we won't be able to import our modules.

Before continuing, let's do some tests by running the e2e test npm run test:e2e and see what will happen:
Error e2e
We got 404 error because, in part 1, we updated the base endpoint (/) to /test, so / is not found. Let's update it, open up app.controller.ts, change @Get('test') to @Get() and run the npm run test:e2e again:
401 error
We still got an error, but now is different. Instead of getting 404 error, we got 401 error which means that the endpoint is not public. So, let's set this endpoint as public by adding @Public() decorator, then run the e2e test again:
Succes e2e test As you can see, now e2e test success ✅

The next step is creating a new file called .env.test by copy-paste our .env (or .env.development in my case) file, then setting the DATABASE_URL to select another database, for example, I select superb-api-test database:
test database
Then, let's run this command to create tables in our test database npx dotenv -e .env.test prisma db push
Test migrated

We need to install config package from Nest JS:
$ npm i --save @nestjs/config

Once the package is installed, we need to register a configuration in our app.module.ts. Let's just open app.module.ts file and make these updates:

imports: [
  // See here. Add config module
  ConfigModule.forRoot({
    envFilePath: process.env.NODE_ENV
      ? `.env.${process.env.NODE_ENV}`
      : '.env',
    isGlobal: true,
  }),

  ...

  UsersModule,
  PostsModule,

  ...
],
Enter fullscreen mode Exit fullscreen mode

Make sure to put ConfigModule at the top of other module imports

The last configuration will be updating test:e2e script in our package.json. Open up package.json and update test:e2e script by adding NODE_ENV=test. Here is the full snippet:
"test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json".

Now let's write our e2e testing code 🔥

Our e2e testing code will be written in test/app.e2e-spec.ts. We'll start with importing some modules; AppModule, UsersModule, PostsModule, PrismaClient, and TRUNCATE all tables as well before running the tests in beforeAll:

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

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

  prismaClient = moduleFixture.get<PrismaClient>(PrismaClient);

  await prismaClient.$executeRaw`TRUNCATE "public"."Post" RESTART IDENTITY CASCADE;`;
  await prismaClient.$executeRaw`TRUNCATE "public"."User" RESTART IDENTITY CASCADE;`;
}, 30000);
Enter fullscreen mode Exit fullscreen mode

Then, after all tests are completed, we need to close the app and disconnect the prismaClient:

afterAll(async () => {
  await app.close();
  await prismaClient.$disconnect();
}, 30000);
Enter fullscreen mode Exit fullscreen mode

Here is the full code:

// test/app.e2e-spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { UsersModule } from 'src/modules/users/users.module';
import { PostsModule } from 'src/modules/posts/posts.module';
import { PrismaClient } from '@prisma/client';

describe('Superb API (e2e)', () => {
  let app: INestApplication;
  let prismaClient: PrismaClient;

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

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

    prismaClient = moduleFixture.get<PrismaClient>(PrismaClient);

    await prismaClient.$executeRaw`TRUNCATE "public"."Post" RESTART IDENTITY CASCADE;`;
    await prismaClient.$executeRaw`TRUNCATE "public"."User" RESTART IDENTITY CASCADE;`;
  }, 30000);

  afterAll(async () => {
    await app.close();
    await prismaClient.$disconnect();
  }, 30000);

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});
Enter fullscreen mode Exit fullscreen mode

Let's start with register and login:

// register

it('should create a user', async () => {
  const user = {
    email: 'newuser@e2e.test',
    name: 'New User',
    password: '12345678',
  };

  const response = await request(app.getHttpServer())
    .post('/users/register')
    .send(user)
    .expect(201);

  expect(response.body).toEqual({
    id: expect.any(Number),
    email: user.email,
    name: user.name,
  });
});
Enter fullscreen mode Exit fullscreen mode
it('should login a user', async () => {
  const user = {
    email: 'newuser@e2e.test',
    password: '12345678',
  };

  const response = await request(app.getHttpServer())
    .post('/users/login')
    .send(user)

    .expect(201);

  expect(response.body).toEqual({
    access_token: expect.any(String),
  });
});

it('should not login a user', async () => {
  const user = {
    email: 'wronguser@e2e.test',
    password: '12345678',
  };

  const response = await request(app.getHttpServer())
    .post('/users/login')
    .send(user)

    .expect(404);

  expect(response.body.message).toEqual('User not found');
  expect(response.body.status).toEqual(404);
});
Enter fullscreen mode Exit fullscreen mode

Okay, we've written e2e test for register and login. Now let's try to run it:
e2e test

All is good. Now you can try to write e2e tests for Posts endpoints by yourself 😁

Now we're done with this part :)

The full code of part 6 can be accessed here:
https://github.com/alfism1/nestjs-api/tree/part-six

Moving on to part 7
https://dev.to/alfism1/build-complete-rest-api-feature-with-nest-js-using-prisma-and-postgresql-from-scratch-beginner-friendly-part-7-6me

Top comments (2)

Collapse
 
jjgolden profile image
James Goldberg

Very nice write up. Thank you for your contribution.

Collapse
 
alfism1 profile image
Alfi Samudro Mulyo

Thanks :)