Scope of discussion:
- Explain about testing
- Create unit testing for
users
So far, we've created two endpoints; users
and posts
.
Users endpoints:
POST /users/register
,
POST /users/login
,
GET /users/me
,
PATCH /users/:id
,
DELETE /users/:id
.
Posts endpoints:
POST /posts
,
GET /posts
,
GET /posts/:id
,
PATCH /posts/:id
,
DELETE /posts/:id
.
We tested it and everything works as expected. Does it mean our API is ready for production? Technically, we can use it as we already tested it during the development process. But we can do something to minimize the occurrence of bugs because does not rule out the possibility that our API will be grown and will have more features in the future.
What we can do? In the software development world, it's common to write code for testing purposes. We'll have advantages for writing testing code (especially in regression testing) that will save our time to do the automated testing instead of doing manual testing one by one from the very beginning. Not just that, writing testing code will make us more confident to release our code to production.
There are some kinds of testing, but we'll only cover unit testing and e2e (end-to-end) testing. Let's focus on unit testing first (we'll do e2e testing in the next part).
If we take a look at our current codes, we can find an example of unit testing, written in a file named app.controller.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
We can try to run the code. In the terminal, go to the root folder of the project and run npm run test
For more details about testing, read the official documentation here: https://docs.nestjs.com/fundamentals/testing
Let's get started 🔥
Make sure to install the testing package $ npm i --save-dev @nestjs/testing
. By default, it's already installed during the Nest JS project initialization in part 1.
Before diving into the code, we need to do a small configuration. For your information, we'll use jest
library (for more information about jest, click here). Open up package.json
on the root project folder, and find jest
property:
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
...
any other config goes here...
...
}
Let's add two properties (testEnvironment
and moduleNameMapper
) inside jest
object:
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
...
"testEnvironment": "node",
"moduleNameMapper": {
"^src/(.*)": "<rootDir>/$1"
}
}
If you don't update your
jest
config, you'll get an error during the import module.
Now, let's write our first unit testing code for users
controller. Create a new file called users.controller.spec.ts
:
// src/modules/users/users.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeAll(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = app.get<UsersController>(UsersController);
});
it('should be defined"', () => {
expect(controller).toBeDefined();
});
describe('users controller', () => {
// 💡 Test code goes here ...
});
});
I updated to
beforeAll
because each test uses the same resource.
then try to run npm run test
again
Normally we will get this error because users.service
is dependent on PrismaService
. So we have to import PrismaService
to our test code:
const app: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [
UsersService,
PrismaService, // 💡 add prisma service here
],
}).compile();
You may wonder why
users.service
orusers.controller
doesn't need to importPrismaService
but in tests, we need them to be imported? In the Nest JS module, we define thePrismaService
globally viaCoreModule
so it can be used in any other modules. Testing code is not part of Nest JS modules, that's why we have to importPrismaService
individually.
Then, try to run npm run test
again after we imported PrismaService
. And again, we still get an error but it's different.
We need to import JwtService
as well to make this work:
const app: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [
UsersService,
PrismaService, // 💡 add prisma service here
{
provide: JwtService, // 💡 add jwt service here
useValue: {
signAsync: jest.fn(), // 💡 mock signAsync method
},
},
],
}).compile();
In unit testing, we will not execute the actual function instead we will just mock the process or result, that's why we mock the
signAsync
function usingjest.fn()
as a replacement for the actual processaccess_token: await this.jwtService.signAsync(payload),
in theusers.service.ts
.
Now let's try again, it should work ✅
We're ready to write our test codes now 🔥
In the users.controller.ts
, we have five functions; registerUser
, loginUser
, me
, updateUser
, and deleteUser
.
We'll try to cover those five functions in unit testing. All the test scenarios will be written inside:
describe('users controller', () => {
// 💡 registerUser method
// 💡 loginUser method
// 💡 me method
// 💡 updateUser method
// 💡 deleteUser method
});
Let's start with registerUser
test first:
// 💡 registerUser method
it('should register new user', async () => {
const newUser = {
email: 'test@user.com',
name: 'Test User',
password: 'password',
};
const mockRegisterResponse: User = {
id: 1,
email: 'test@user.com',
name: 'Test User',
password: 'password',
};
// delete password from response
delete mockRegisterResponse.password;
// 💡 See here -> we mock registerUser function from users.controller.ts
// to return mockRegisterResponse
jest
.spyOn(controller, 'registerUser')
.mockResolvedValue(mockRegisterResponse);
// 💡 See here -> we call registerUser method from users.controller.ts
// with newUser as parameter
const result = await controller.registerUser(newUser);
// 💡 See here -> we expect result to be mockRegisterResponse
expect(result).toEqual(mockRegisterResponse);
});
In the code above, we've written the success register test and if you run npm run test
will be marked as a success.
We can write for the failed register test as well:
// 💡 test register error due to email already registered
it('should throw error if email already registered', async () => {
const registeredUser = {
email: 'registered@user.com',
name: 'Registered User',
password: 'password',
};
jest
.spyOn(controller, 'registerUser')
.mockRejectedValue(new ConflictException());
const register = controller.registerUser(registeredUser);
await expect(register).rejects.toThrow(ConflictException);
});
// 💡 test register error due to missing some fields
it('should throw error if required fields is missing', async () => {
jest
.spyOn(controller, 'registerUser')
.mockRejectedValue(new BadRequestException());
const register = controller.registerUser(null);
await expect(register).rejects.toThrow(BadRequestException);
});
Quick overview. We created three test cases for register:
- test register success,
- test register error due to email already registered, and
- test register error due to missing some fields.
Let's continue to loginUser
. First, we'll create the success login test case:
// 💡 test login success
it('should login user', async () => {
const mockLoginResponse = {
access_token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyeyey',
};
// 💡 See here -> we mock loginUser function from users.controller.ts
// to return mockLoginResponse
jest
.spyOn(controller, 'loginUser')
.mockResolvedValue(mockLoginResponse);
// 💡 See here -> we call loginUser method from users.controller.ts
// with mockLoginResponse as parameter
const result = await controller.loginUser({
email: 'some@user.com',
password: 'password',
});
expect(result).toEqual(mockLoginResponse);
expect(result.access_token).toBeDefined();
});
and test case for login error with the wrong email:
// 💡 test login error due to wrong email
it('should throw error if email is wrong', async () => {
const wrongEmail = {
email: 'wrong@user.com',
password: 'password',
};
jest
.spyOn(controller, 'loginUser')
.mockRejectedValue(new NotFoundException());
const login = controller.loginUser(wrongEmail);
await expect(login).rejects.toThrow(NotFoundException);
});
Quick overview. We created two test cases for login:
- test login success
- test login error due to wrong email
We're done with loginUser
and registerUser
test cases. There are still three methods remaining; me
, updateUser
, and deleteUser
.
I won't write all the test cases in this article. But don't worry. The full version is in my GitHub repository 🔗
Check the user full test code here: https://github.com/alfism1/nestjs-api/blob/part-four/src/modules/users/users.controller.spec.ts
Now we're done with this part :)
The full code of part 5 can be accessed here:
https://github.com/alfism1/nestjs-api/tree/part-five
Moving on to part 6
https://dev.to/alfism1/build-complete-rest-api-feature-with-nest-js-using-prisma-and-postgresql-from-scratch-beginner-friendly-part-6-c51
Top comments (0)