NestJS — Test Driven Development (1)

이전에 쓰던 To Do List를 폐기하고, NestJS MVC 환경에서 TDD를 수행하는 법을 작성하려 한다. 크게 Unit Test와 Integration Test로 나누어서 연재할 예정이다. 흔히 서비스의 프론트엔드에서 발생하는 요청

dev-whoan.xyz

계속해서 User 정보를 관리하는 API 서버를 설계하고 개발하겠다.

먼저 TDD를 수행할 때, 정확히는 어떤 서비스를 개발할 때 우리가 무엇을 개발할 것인지 분석해야한다. 쉽게 설명하면, 건물을 지을때 도 설계도를 먼저 만든 뒤 이를 기반으로 건물을 짓는다.

마찬가지로 소프트웨어 또한 개발할 때, 무작정 코드를 작성하기 시작할 것이 아니라 필요한 기능들을 정의한, 내가 만들고자 하는 서비스는 어떤 기능을 제공하는지 그리고 각 기능을 제공하기 위해 무엇이 필요한지 분석하고, 이를 바탕으로 소프트웨어를 개발해 나가기 시작해야 한다.

본 글은 TDD와 관련된 글이기 때문에, 분석이 어떻게 진행되는지와 관련해서 자세히 설명하지 않겠다. 나중에 필요할 경우 해당 글을 연재하겠다. 쉽게 소프트웨어개발방법론 이라고 불리는데, 우리가 흔히 아는 Waterfall, Agile, Unified Process 등이 그 예시이다.

TDD는 간단히 말해 이렇게 우리가 분석한 요구사항, Requirements들에 대해 어떻게 되어야 기능 개발에 성공한것인지, 어떤 실패 케이스가 있는지 찾고 실제로 코드를 실행하여 내가 분석한 Requirements, 즉 해당 기능이 잘 동작하는지 확인하는 것이다.


우리는 User 정보를 관리하는 API를 만들것인데, 간단히 CRUD 위주의 기능을 분석, 설계하고 개발하겠다.

CRUD를 위주로, 우리는 4개의 Primary Requirements를 찾을 수 있다. 만약 RESTful API에 대해 잘 모른다면, 아래 글을 참고하자.

 

[RESTful API 설계하기] 1. RESTful과 API / RESTful API란

[RESTful API 설계하기] 2. REST 특징 [RESTful API] 1. RESTful과 API 어떤 서비스를 개발할 때 본래 필수적인 기능은 아니었지만, 이제는 필수적인 기능이 되어버린 API와 관련하여 글을 작성하려 한다. 이 중

dev-whoan.xyz

Create: 새로운 User를 만들 수 있어야 한다.
Read: 기존의 User 정보를 읽을 수 있어야 한다.
Update: 기존의 User갱신할 수 있어야 한다.
Delete: 기존의 User삭제할 수 있어야 한다.

RESTful API를 설계할 것이기 때문에, 각 CRUD에 대응하는 HTTP Request 및 간략한 요구사항 분석은 다음과 같다.

Create

POST
/user

  • 새로운 User를 생성한다. 이 때 Body Parameter를 통해 새로운 User를 생성하는데 필요한 정보를 전달받는다.
  • 만약, 생성에 성공했을 경우 HTTP 201 Created를 반환한다.
  • 만약, 생성 요청된 email을 가지는 User가 이미 존재할 경우 HTTP 409 Conflict를 반환한다.

Read

GET
/user/email/{email}

  • 기존의 User 정보를 읽어온다. 이 때 특정 User를 구분하기 위해서는 email을 전달받고, 해당 User를 찾아 return한다. 이 때 HTTP 응답 코드는 HTTP 200 Ok를 반환한다.
  • 만약, User가 존재하지 않을 경우 HTTP 204 Empty를 반환한다.

Update

PUT
/user/email/{email}

  • 기존의 User 정보를 갱신한다. 이 때 User를 특정하기 위해 email을 구분자로 사용한다.
  • 만약, 해당하는 email을 가진 User가 없을 경우 HTTP 400 Bad Request를 응답한다.
  • PUT Method이지만, 새로운 Content를 생성하는 경우는 없다.

DELETE

DELETE
/user

  • 기존의 User를 삭제한다. 이 때 User를 특정하기 위해 email을 Body Parameter로 전달받는다.
  • User 삭제에 성공한 경우, HTTP 200 OK와 함께 삭제된 유저의 수를 반환한다.
  • 만약 해당하는 User가 없을 경우 HTTP 204 No Content를 반환한다.

위를 살펴보면, 우리는 성공여부와 실패여부를 미리 가정했다. 뿐만 아니라, 어떤 역할을 해야하는지 Informal한 형태로 분석했다. 이를 통해 우리는 CRUD를 구현할 때, 해당 기능이 어떤것을 수행하는지 미리 알 수 있다. 더 나아가 구현하지 않았음에도, 어떤 응답을 받을지 이미 알고있다.

이를 쉽게 말하면, 우리는 Test Case를 구현하기 위해 어떤 행동들을 해야 하는지 벌써 알고있다.

이제 실제 구현을 위해 코드를 작성해보자. 시스템 구현을 위한 외부 소프트웨어는 다음과 같다.

  • MongoDB, Mongoose
    • MongoDB: User 정보를 저장하는데 사용될 Database
    • Mongoose: MongoDB를 위한 TypeODM Library.
  • Redis
    • MongoDB에서 가져온 Data를 캐싱하는데 사용할 Memory Database

사실 Redis는 굳이 사용하지 않아도 상관없지만, 요즘은 Redis를 사용하는 것이 추세이기 때문에, 작성하였다.

우리 시스템의 생김새는 다음과 같다.

Repository도 사용할까 했는데, TDD가 주 목적이기 때문에 제외하였다. Controller를 통해 user에 대한 요청이 발생하면, Service를 통해 Model로 부터 데이터를 생성,반환,갱신,삭제 를 수행한다. 이 때 Redis를 이용해 만약 데이터가 캐싱되어 있을 경우, 해당 데이터를 반환한다.

이전 글에서도 말했지만, 우리는 Test를 다음과 같이 수행할 예정이다.

Unit Test

  • Service가 Model로부터 데이터를 잘 가져오는지 테스트
  • 즉 Service를 테스트하기 위한 Controller 테스트 코드 작성
    Integration Test
  • Controller를 통해 요청을 보낼 때, 데이터를 잘 View로 반환하는지 테스트
  • 즉 E2E 테스트 수행

사용할 user의 schema는 다음과 같다.

import { Prop, SchemaFactory, SchemaOptions } from '@nestjs/mongoose';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { Document } from 'mongoose';

const options: SchemaOptions = {
    collection: 'users-tdd',
  timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' },
  id: true,
};

export class User extends Document {
    //* User의 Email
  @Prop({
    require: true,
    unique: true,
  })
  @IsString()
  @IsEmail()
  @IsNotEmpty()
  email: string;

    //* User가 사용할 Nickname
  @Prop({
    require: true,
  })
  @IsString()
  @IsNotEmpty()
  nickname: string;

    //* User가 사용할 Password
  @Prop({
    require: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

  //* Let Redis Use This DTO
  //* Check redis-manager-service.ts
  readonly readOnlyData: {
    id: string;
    email: string;
    nickname: string;
  };
}

//* Virtual Fields
export const _UserSchema = SchemaFactory.createForClass(User);

_UserSchema.virtual('readOnlyData').get(function (this: User) {
  return {
    id: this.id,
    email: this.email,
    nickname: this.nickname,
  };
});

_UserSchema.set('toObject', { virtuals: true });
_UserSchema.set('toJSON', { virtuals: true });

export const UserSchema = _UserSchema;

export interface UserReadOnly {
  id: string;
  email: string;
  nickname: string;
}

Service Code 작성

Unit Test를 진행할 때, 현재 테스트 할 기능 단위에 필요한, 의존성을 갖는 기능들은 모두 잘 동작한다고 가정하고 테스트를 진행한다. 그래서 실제로는 User의 코드와 User에서 사용할 Redis의 코드는 별개로, Redis는 주어진 Interface대로 잘 동작할 것이다. 고 가정한다. 하지만 우리는 아직 Unit Test를 위해 이러한 가정을 어떻게 만드는지 모르기 때문에, Redis를 통해 먼저 Mock Data (Object)를 만드는 것을 연습한 뒤, User를 통해 실제 Unit Test를 진행하겠다. 즉 오늘은 Redis를 통해 Mock과 친해지고, 다음 글에서 본격적인(사실 이번 글도 본격적인 TDD다.) TDD를 진행하겠다.

Redis의 사용 목적은 Data Caching을 통해 Server 및 DB의 부하를 줄이는 것이다. 즉, 다음과 같은 기능을 수행할 수 있어야 한다.

Set Cache: DB로 부터 가져온 데이터를 캐싱할 수 있어야 한다.
Get Cache: 저장된 캐시를 반환할 수 있어야 한다.
Delete Cache: 저장된 캐시를 삭제할 수 있어야 한다.
Reset Cache: 현재 저장된 모든 캐시를 초기화할 수 있어야 한다.

이에 대해 함수를 다음과 같이 설계하겠다.

interface setCache {
  (key: string, value: User): boolean;
}

interface getCache {
  (key: string): User;
}

interface deleteCache {
  (key: string): boolean;
}

interface resetCache {
  (): boolean;
}

인터페이스의 경우 위처럼 생겼는데, 사실 그냥 넣어봤다. 실제 코드를 구현하면 다음과 같다.

따로 작성 설명을 하지 않는 이유는, nestjs에서 redis를 사용할 때, 외부 라이브러리를 통해 빠르게 사용할 수 있고, 무엇보다 https://docs.nestjs.com/microservices/redis#redis 여기에 가면 자세하게 나온다.

redis-manager.module.ts

import { CacheModule, Module } from '@nestjs/common';
import { RedisManagerService } from './redis-manager.service';
import * as redisStore from 'cache-manager-ioredis';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',
      port: 6399,
      ttl: 0,
    }),
  ],
  providers: [RedisManagerService],
  exports: [RedisManagerService],
})
export class RedisManagerModule {}

redis-manager.service.ts

import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { User } from '../user/data/user.schema';

@Injectable()
export class RedisManagerService {
  constructor(
    @Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,
  ) {}
  /**
   * Set the object in Redis
   * @param key Key to identify the object
   * @param value User to set for the key
   * @returns true when succeeded
   */
  async setCache(key: string, value: User): Promise<boolean> {
    await this.cacheManager.set(key, value);
    return true;
  }

  /**
   * Return the object that matches key in Redis
   * @param key Key to identify the object
   * @returns User when the item exist in Redis
   * @returns undefined when the item is not exist in Redis
   */
  async getCache(key: string): Promise<User> {
    const result = await this.cacheManager.get(key);
    if (!result) {
      return null;
    }

    return result as User;
  }

  /**
   * Delete the object that matches key in Redis
   * @param key Key to delete from Redis
   * @returns true when succeeded
   */
  async deleteCache(key: string): Promise<boolean> {
    await this.cacheManager.del(key);
    return true;
  }

  /**
   * Reset all the data stored in Redis
   * @returns true when succeeded
   */
  async resetCache(): Promise<boolean> {
    await this.cacheManager.reset();
    return true;
  }
}

주석을 달아놓았기 때문에 코드를 읽는데 문제 없을것이라 생각한다.


이제 Redis에 대한 Unit Test를 진행해보자. 이 때 먼저 고려해야할 것은 Redis를 사용하기 위한 의존 기능은 무엇이 있는가?이다. 이것을 찾지 못한다면 Redis에 대한 TDD를 수행하기 어렵다.

NestJS는 고맙게도 해당 모듈이 어떤 의존성을 가지는지 우리는 쉽게, 정확히는 이미 알고있다.

먼저 redis-manager.module.ts 를 통해 우리의 Redis 모듈이 외부의 어떤 기능을 제공받는지 확인할 수 있다.

...
  **imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',
      port: 6399,
      ttl: 0,
    }),
  ],
    providers: [],**
...

이는 Redis Module이 동작하기 위해 필요한 외부의 Dependency이다. 해당 기능이 하나라도 동작하지 않는다면, Redis Module은 실행되지 않을것이다. (앱 자체가 실행되지 않을것이다.)

현재 우리는 Redis에 대한 Unit Test를 수행할 것이고, 따라서 대상인 redis-manager.service.ts에 어떤 의존성이 존재하는지 확인해보자.

export class RedisManagerService {
  constructor(
    **@Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,**
  ) {}
...
}

Redis Service는 cacheManager 라는 것을 의존성으로 갖는데, 이는 CACHE_MANAGER로, 다음의 역할을 수행한다.

cacheManager.set: Redis에 캐시를 저장한다.
cacheManager.get: Redis로 부터 캐시를 획득한다.
cacheManager.del: Redis로 부터 캐시를 삭제한다.
cacheManager.reset: Redis의 캐시를 삭제한다.

즉, 해당 의존성을 통해 실제 레디스에 데이터를 저장하고 삭제한다.

위를 토대로 우리는 redis-manager.module.tsredis-manager.service.ts로 부터 다음 의존성을 찾을 수 있다.

  1. Redis 연결을 수행하기 위한 CacheModule.register 동작이 필요하다.
  2. Redis에 실제 데이터를 저장,삭제,획득,초기화 등을 수행하는 CACHE_MANAGER가 필요하다.

이제 진짜 Redis의 Test 코드를 작성해 보자. 지난 글을 바탕으로, 우리는 다음과 같은 초기 코드를 작성할 수 있다.

import { Test } from '@nestjs/testing';
import { RedisManagerService } from '../redis-manager.service';

describe('RedisManagerController', () => {
  let service: RedisManagerService;
  const key = 'file/yes-data';

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [],
    }).compile();

    service = moduleRef.get<RedisManagerService>(RedisManagerService);
  });
});

하지만 위 코드대로 test를 수행한다면, 다음 에러가 발생할 것이다.

npm test redis-manager.controller                                                                                                                 1 ✘  23:44:22 

> tdd@0.0.1 test
> jest "redis-manager.controller"

 FAIL  src/redis-manager/test/redis-manager.controller.spec.ts
  RedisManagerController
    ✕ should be defined (5 ms)

  ● RedisManagerController › should be defined

    **Nest can't resolve dependencies of the RedisManagerService (?). Please make sure that the argument CACHE_MANAGER at index [0] is available in the RootTestModule context.

    Potential solutions:
    - Is RootTestModule a valid NestJS module?
    - If CACHE_MANAGER is a provider, is it part of the current RootTestModule?
    - If CACHE_MANAGER is exported from a separate @Module, is that module imported within RootTestModule?
      @Module({
        *imports: [ /* the Module containing CACHE_MANAGER */ ]*
      })**

       7 |
       8 |   beforeEach(async () => {
    >  9 |     const moduleRef = await Test.createTestingModule({
         |                       ^
      10 |       providers: [RedisManagerService],
      11 |     }).compile();
      12 |

      at TestingInjector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:247:19)
      at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:200:33)
      at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/testing/testing-injector.js:19:45)
      at resolveParam (../node_modules/@nestjs/core/injector/injector.js:120:38)
          at async Promise.all (index 0)
      at TestingInjector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:135:27)
      at TestingInjector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:61:13)
      at TestingInjector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:88:9)
      at ../node_modules/@nestjs/core/injector/instance-loader.js:49:13
          at async Promise.all (index 3)
      at TestingInstanceLoader.createInstancesOfProviders (../node_modules/@nestjs/core/injector/instance-loader.js:48:9)
      at ../node_modules/@nestjs/core/injector/instance-loader.js:33:13
          at async Promise.all (index 1)
      at TestingInstanceLoader.createInstances (../node_modules/@nestjs/core/injector/instance-loader.js:32:9)
      at TestingInstanceLoader.createInstancesOfDependencies (../node_modules/@nestjs/core/injector/instance-loader.js:21:9)
      at TestingInstanceLoader.createInstancesOfDependencies (../node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
      at TestingModuleBuilder.createInstancesOfDependencies (../node_modules/@nestjs/testing/testing-module.builder.js:97:9)
      at TestingModuleBuilder.compile (../node_modules/@nestjs/testing/testing-module.builder.js:63:9)
      at Object.<anonymous> (redis-manager/test/redis-manager.controller.spec.ts:9:23)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.484 s, estimated 2 s
Ran all test suites matching /redis-manager.controller/i.

해당 오류를 자세히 보면, Redis Module은 CACHE_MANAGER를 의존성으로 갖는데, 찾을 수 없다는 내용이다.

Nest can't resolve dependencies of the RedisManagerService (?). Please make sure that the argument CACHE_MANAGER at index [0] is available in the RootTestModule context.

imports: [ / the Module containing CACHE_MANAGER / ]

따라서, 우리는 CACHE_MANAGER에 대한 Mock 을 만들어줘야 한다.

다시 말하면, cache-manager, cache-manager-ioredis가 수행하는 Redis Software에 데이터를 저장하고, 읽고, 삭제하는 등의 Test Mockup을 만들어야 한다.


Mockup을 만들기 전에, 내가 사용하는 Test Directory 구조는 다음과 같다.

 

  • mocks
    • Mock과 관련된 자료형, 함수, 클래스 등을 저장할 폴더
  • test
    • 실제 test 코드를 작성할 폴더

이외에도 다음이 있다.

  • data
    • 해당 기능이 가지는 data(schema, repository, dto, … 등)을 저장하는 폴더
  • test/stubs
    • 해당 기능을 테스트하는데 필요한 mock 객체를 저장할 폴더

본격 Mock 만들기 — User Mock Data

Mock
모조품, 가짜

Mock을 만드는 이유는 우리가 테스트할 기능에 필요한 외부 기능(의존성)이 마치 잘 동작하는것처럼 보여주기 위해 만드는 것이다. 다시 정리하자면, 우리 시스템의 Redis는 cache-manager-ioredis, cache-manager 라이브러리가 제공하는 실제 Redis 소프트웨어 연결 및 실제 데이터 작성, 읽기, 삭제 등이 잘 동작하는 것으로 가정해야 하기 때문이다. 우리는 Redis Manager Module을 작성함으로써 User Module(더 나아가 Redis를 이용하는 모든 모듈)에서 발생하는 이러한 캐싱 동작에 대해, Redis 소프트웨어와 실제 통신함으로써 그 동작이 수행되도록 하는 역할을 한다. 따라서, 실제 테스트 코드에서는 이러한 연결 및 Redis의 라이브러리에서 제공하는 기능(get, set, ...)은 100% 잘동작한다고 가정, 우리의 코드 setCache, getCache, ...를 테스트하기 위해 만드는 것이다. 즉, 이러한 가정을 제공하기 위한 Mock을 만드는 것이다.

우리는 먼저 Redis의 CACHE_MANAGER에 대한 Mock, 즉 진짜처럼 동작하는 가짜를 만들어야한다. 그런데 생각해보니 Redis는 Cache Manager를 통해 User를 캐싱하고, 획득하고, 삭제하는데 이때 User와 관련된 자료형이 필요하다.

따라서 우리는 Redis 기능을 만족시키기 위해 User의 Mock Data 또한 만들어줘야 한다.

사용할 User는 우리가 위에서 정의한 user.schema.ts의 모든 Property를 가져야 하고, 동시에 필요한 자료만 있으면 되기 때문에 다음과 같이 정의하겠다.

해당 파일은 user/test/stubs/user.stub.ts다.

import { User } from '../../../user/data/user.schema';

//* 아래의 속성들은 분명 user.schema.ts에 정의되어있다.
export const mockUserDto: any = {
  id: 'test-id',
  email: 'test@test.com',
  nickname: 'test-nickname',
    password: 'test-password',
};

//* mockUserStub을 통해 User 모조품을 반환한다.
export const mockUserStub = (): User => {
  return mockUserDto;
};

우리는 mockUserDto를 통해 하나의 Dto(자료형)를 나타낼 것이고, mockUserStub을 통해 실제 User가 반환되는 결과를 나타낼 것이다.

본격 Mock 만들기 — CACHE_MANAGER

우리의 CACHE_MANAGER는 위에서 찾은것과 같이 set,get,del,reset을 수행하고, 다음과 같다.

이 때 주의깊게 살펴볼 것은 get을 통해 User를 반환하는데, 위에서 정의한 mockUserStub을 그 결과로 반환하는 것에 집중하자.

우리는 해당 CACHE_MANAGER를 Value 형태로 작성하여 사용할 것이다.

import { mockUserStub } from '../../user/test/stubs/user.stub';

export const CACHE_MANAGER = {
  set: jest.fn().mockResolvedValue(true),
    //* 위에서 정의한 Test용 가짜 User를 반환하고 있다!
  **get: jest.fn().mockResolvedValue(mockUserStub()),**
  del: jest.fn().mockResolvedValue(true),
  reset: jest.fn().mockResolvedValue(true),
};

이제 가짜 CACHE_MANAGER를 만들었으니 이것을 우리가 수행할 test code에서 제공해야한다.

즉, redis-manager.controller.spec.ts의 모듈을 만드는 과정에서 해당 CACHE_MANAGER를 DI해줘야 한다. 우리는 **기능을 제공하기 때문에 provides 아래에 제공하면 된다.**

이 때 imports에 사용할 것인지 등은 우리의 코드에 필요에 따라 바꾸면 된다.

//* 변경 전
...
let service: RedisManagerService;
...
const moduleRef = await Test.createTestingModule({
  providers: [
    RedisManagerService,
  ],
}).compile();
service = moduleRef.get<RedisManagerService>(RedisManagerService);
...

//* 변경 후
import { CACHE_MANAGER as MOCK_CACHE_MANAGER } from '../__mocks__/redis-manager.service';
...
let service: RedisManagerService;
**let cache: Cache;**
...
const moduleRef = await Test.createTestingModule({
  providers: [
    **{
      provide: CACHE_MANAGER,
      useValue: MOCK_CACHE_MANAGER,
    },**
    RedisManagerService,
  ],
}).compile();
service = moduleRef.get<RedisManagerService>(RedisManagerService);
**cache = moduleRef.get(CACHE_MANAGER);**
...

이 때 provide할 대상에 대해 useValue, useClass, useFactory 등을 사용할 수 있는데, 그 차이는 다음과 같다.

https://docs.nestjs.com/fundamentals/testing

  • useClass: 객체(제공자, 가드 등)를 재정의할 인스턴스를 제공하기 위해 인스턴스화될 클래스를 제공합니다.
  • useValue: 객체를 재정의할 인스턴스를 제공합니다.
  • useFactory: 객체를 재정의할 인스턴스를 반환하는 함수를 제공합니다.

이제 다시 한 번 npm test redis-manager.controller를 실행해 보자.


Unit Test 코드 작성하기

이제 실제 Redis가 잘 동작하는지, Redis에 정의된 setCache, getCache, delCache, resetCache를 테스트하는 코드를 작성하면 된다.

import { CACHE_MANAGER } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { Cache } from 'cache-manager';
import { User } from '../../user/data/user.schema';
import { mockUserDto, mockUserStub } from '../../user/test/stubs/user.stub';
import { RedisManagerService } from '../redis-manager.service';
import { CACHE_MANAGER as MOCK_CACHE_MANAGER } from '../__mocks__/redis-manager.service';

describe('RedisManagerController', () => {
  let service: RedisManagerService;
  let cache: Cache;
  const key = 'file/yes-data';

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        {
          provide: CACHE_MANAGER,
          useValue: MOCK_CACHE_MANAGER,
        },
        RedisManagerService,
      ],
    }).compile();

    service = moduleRef.get<RedisManagerService>(RedisManagerService);
    cache = moduleRef.get(CACHE_MANAGER);
  });

  it('then it should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('when setCache is called', () => {
    let data: boolean;

    beforeEach(async () => {
      data = await service.setCache(key, mockUserDto as User);
    });

    test('then it should call redis.set', () => {
      expect(cache.set).toBeCalledWith(key, mockUserDto as User);
    });

    test('then it should return a "true"', () => {
      expect(data).toEqual(true);
    });
  });

  describe('when getCache is called', () => {
    let data: User;

    //* Call the function through the controller
    beforeEach(async () => {
      data = await service.getCache(key);
    });

    //* Controller may call the function through the service
    test('then it should call redis.get', () => {
      //* With the Given Parameter
      expect(cache.get).toBeCalledWith(key);
    });

    //* And the result should be microServiceGetDataStub()
    //* Which is Mock Data
    test('then it should return a User Info', () => {
      expect(data).toEqual(mockUserStub());
    });
  });

  describe('when deleteCache is called', () => {
    let data: boolean;

    beforeEach(async () => {
      data = await service.deleteCache(key);
    });

    test('then it should call redis.del', () => {
      expect(cache.del).toBeCalledWith(key);
    });

    test('then it should return a "true"', () => {
      expect(data).toEqual(true);
    });
  });

  describe('when resetCache is called', () => {
    let data: boolean;

    beforeEach(async () => {
      data = await service.resetCache();
    });

    test('then it should call redis.reset', () => {
      expect(cache.reset).toBeCalledWith();
    });

    test('then it should return a "true"', () => {
      expect(data).toEqual(true);
    });
  });
});
  • 결과

P.S. 본 글에 사용된 dependency 설치

npm install @nestjs/config
@nestjs/microservices
npm install --save class-validator class-transformer
npm install @nestjs/mongoose mongoose
npm install cache-manager cache-manager-ioredis
npm install -D @types/cache-manager @types/cache-manager-ioredis

' > NestJS' 카테고리의 다른 글

NestJS — Test Driven Development (1)  (0) 2023.03.10

+ Recent posts