Scope of discussion:
- Explain about e2e (end-to-end) testing
- Handle multiple
env
files - 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"
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"
}
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:
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:
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:
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:
Then, let's run this command to create tables in our test database npx dotenv -e .env.test prisma db push
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,
...
],
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);
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);
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!');
});
});
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,
});
});
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);
});
Okay, we've written e2e test for register
and login
. Now let's try to run it:
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)
Very nice write up. Thank you for your contribution.
Thanks :)