NestJs Chapter 4 (Jest)

yeopยท2022๋…„ 7์›” 18์ผ

Nest JS ์ •๋ฆฌ

๋ชฉ๋ก ๋ณด๊ธฐ
4/10

Unit Testing the User Service

๐Ÿ”ถ Jest Path Error

TypeScript์—์„œ๋Š” ์ ˆ๋Œ€๊ฒฝ๋กœ๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์ง€๋งŒ jest๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฒฝ๋กœ๋ฅผ ์ดํ•ดํ•˜์ง€ ๋ชปํ•œ๋‹ค.
Cannot find module 'src/common/entities/core.entity' from 'users/entities/user.entity.ts'

๐Ÿ”ธ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

// package.json

  "jest": {
    "moduleNameMapper": {
     "^src/(.*)$": "<rootDir>/$1"
   },
   "moduleDirectories": [
     "src",
     "node_modules"
   ],
  }

๐Ÿ”ท Mocking

๐Ÿ”น getRepositoryToken()

์œ ๋‹› ํ…Œ์ŠคํŠธ์—์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ํ”ผํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€ํ•˜๊ณ  ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ตœ๋Œ€ํ•œ ๋น ๋ฅด๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ๋ฅผ ์›ํ•œ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ์—ฐ๊ฒฐ ์ธ์Šคํ„ด์Šค์—์„œ ๊ฐ€์ ธ์˜จ Repository ๋Œ€์‹ ์— Mock Repository๋ฅผ ๋งŒ๋“ค์–ด custom providers๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ํด๋ž˜์Šค๊ฐ€ @InjectRepository() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UsersRepository๋ฅผ ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค Nest๋Š” ๋“ฑ๋ก๋œ mockRepository ๊ฐ์ฒด๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•œ๋‹ค.
https://docs.nestjs.com/techniques/database#testing

describe('UserService', () => {
  let service: UserService;
  let usersRepository: MockRepository<User>;

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useValue: mockRepository,
        },
        {
          provide: getRepositoryToken(Verification),
          useValue: mockRepository,
        },
        {
          provide: JwtService,
          useValue: mockJwtService,
        },
        {
          provide: MailService,
          useValue: mockMailService,
        },
      ],
    }).compile();
    service = module.get<UserService>(UserService);
    usersRepository = module.get(getRepositoryToken(User));
  });

๐Ÿ”น jest.fn()

it('should fail if the password is wrong', async () => {
      const mockedUser = {
        checkPassword: jest.fn(() => Promise.resolve(false)),
      };
      usersRepository.findOne.mockResolvedValue(mockedUser);
      const result = await service.login(loginArgs);
      expect(result).toEqual({ ok: false, error: 'Wrong Password' });
    });
  • jest.fn(implements)
    jest.fn ๋‚ด๋ถ€์— ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ mockResolvedValue์™€ ๊ฐ™์ด ํ•ด๋‹น line์„ ๊ฐ€๋กœ์ฑ„๋Š” ๊ธฐ๋Šฅ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
const mockRepository = {
  findOne: jest.fn(),
  save: jest.fn(),
  create: jest.fn(),
};

const mockJwtService = {
  sign: jest.fn(),
  verify: jest.fn(),
};

const mockMailService = {
  sendVerificationEmail: jest.fn(),
};

๐Ÿ”น Record

  • ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋ฐ›์•„์™€์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  mock Repository๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด ๋•Œ mock Repository์• ์„œ๋„ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋ฐ›์•„์™€์„œ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๊ฐ™์ด Repository ๋‚ด์žฅ ํ•จ์ˆ˜๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ๋งŒ ํ•œ๋‹ค. ์ด ๋‚ด์žฅ ํ•จ์ˆ˜ ๋˜ํ•œ ๋ชจ๋‘ mock type์œผ๋กœ ๋งŒ๋“ค๋ฉฐ ๊ทธ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด Record์ด๋‹ค.

  • Record๋Š” ์†์„ฑ ํ‚ค๊ฐ€ Key์ด๊ณ  ์†์„ฑ ๊ฐ’์ด Type์ธ ๊ฐ์ฒด ์œ ํ˜•์„ ๊ตฌ์„ฑํ•œ๋‹ค.
    ์ด ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” ์œ ํ˜•์˜ ์†์„ฑ์„ ๋‹ค๋ฅธ ์œ ํ˜•์— ๋งคํ•‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

๐Ÿ”น Reason of Mocking

- .mockfn.mockResolvedValue()

์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ํ…Œ์ŠคํŠธํ•˜๊ณ ์ž ํ•˜๋Š” ํ•จ์ˆ˜(createAccount)๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— mockfn.mockResolvedValue๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด userService์˜ createAccount ์‹คํ–‰์— ์“ฐ์ด๋Š” findOne line์„ ๊ฐ€๋กœ์ฑ„ mock result๋ฅผ return ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ๋‹ค๋ฅธ function๊ณผ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ํ•จ์ˆ˜ ์ž์ฒด๋งŒ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ์ด์œ ๋Š” integration test๊ฐ€ ์•„๋‹Œ unit test, ํ•จ์ˆ˜ ํ•˜๋‚˜๋งŒ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

describe('createAccount', () => {
    it('Should fail if user exists', async () => {
      usersRepository.findOne.mockResolvedValue({
        id: 1,
        email: 'exist@naver.com',
      });
      const result = await service.createAccount({
        email: '',
        password: '',
        role: 0,
      });
      expect(result).toMatchObject({
        ok: false,
        error: 'There is a user with that email already',
      });
    });
  });

๐Ÿ”ท npm run test:cov

npm run test:cov๋Š” ํ…Œ์ŠคํŠธ์˜ ์ง„ํ–‰์ƒํ™ฉ(์–ด๋– ํ•œ ํŒŒ์ผ์— ๋ช‡ ํผ์„ผํŠธ๊ฐ€ ํ…Œ์ŠคํŠธ ๋˜์—ˆ๊ณ  ๋ช‡ ๋ฒˆ์งธ ์ค„์ด ํ…Œ์ŠคํŠธ ๋˜์—ˆ๋Š”์ง€)๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด ์›์น˜ ์•Š๋Š” ํŒŒ์ผ์˜ ์ง„ํ–‰์ƒํ™ฉ๊นŒ์ง€ ๋ชจ๋‘ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ œํ•œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” package.json์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์•ผํ•œ๋‹ค.

"coveragePathIgnorePatterns": [
      "node_modules",
      ".entity.ts",
      ".constants.ts"
    ]

๐Ÿ”ท mockResolvedValue VS mockReturnValue

  • await๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ค„์€ ํ•˜๋‚˜์˜ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— mockResolvedValue๋ฅผ ๋ฆฌํ„ดํ•˜๊ฒŒ ํ•˜๊ณ  ๊ทธ๊ฒŒ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ์—์„œ mockReturnValue๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
	usersRepository.findOne.mockResolvedValue(undefined);
    usersRepository.create.mockReturnValue(createAccountArgs);

๐Ÿ“‘ UserService Testing Code

https://github.com/Ju-yeop/nuber-eats-backend/blob/main/src/users/users.service.spec.ts

0๊ฐœ์˜ ๋Œ“๊ธ€