Build Complete REST API Feature with Nest JS (Using Prisma and Postgresql) from Scratch - Beginner-friendly - PART 5
Tags: nestjs, typescript, api, backend
Published: 2023-10-29 07:56:50

## Scope of discussion:
1. Explain about testing
2. 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`:
```typescript
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`
![Run the test code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r2wsxywg6guzq68mea8x.png)

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](https://jestjs.io/)). Open up `package.json` on the root project folder, and find `jest` property:
```json
"jest": {
  "moduleFileExtensions": [
    "js",
    "json",
    "ts"
  ],
  "rootDir": "src",
  ...
  any other config goes here...
  ...
}
```
Let's add two properties (`testEnvironment` and `moduleNameMapper`) inside `jest` object:
```json
"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`:
```typescript
// 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
![Prisma error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rji95ctuzkopr8wchd2g.png)

Normally we will get this error because `users.service` is dependent on `PrismaService`. So we have to import `PrismaService` to our test code:
```typescript
const app: TestingModule = await Test.createTestingModule({
  controllers: [UsersController],
  providers: [
    UsersService,
    PrismaService, // 💡 add prisma service here
  ],
}).compile();
```

> You may wonder why `users.service` or `users.controller` doesn't need to import `PrismaService` but in tests, we need them to be imported? In the Nest JS module, we define the `PrismaService` globally via `CoreModule` so it can be used in any other modules. Testing code is not part of Nest JS modules, that's why we have to import `PrismaService` individually.

Then, try to run `npm run test` again after we imported `PrismaService`. And again, we still get an error but it's different.
![jwt service error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfczbrxh67ja7ntezyjf.png)
We need to import `JwtService` as well to make this work:
```typescript
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 using `jest.fn()` as a replacement for the actual process `access_token: await this.jwtService.signAsync(payload),` in the `users.service.ts`.

Now let's try again, it should work ✅

![Success run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn3tpx60f421agoy4if4.png)

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:
```typescript
describe('users controller', () => {
  // 💡 registerUser method

  // 💡 loginUser method

  // 💡 me method

  // 💡 updateUser method

  // 💡 deleteUser method
});
```

Let's start with `registerUser` test first:
```typescript
// 💡 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:
```typescript
// 💡 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:
```typescript
// 💡 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:
```typescript
 // 💡 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