> 웹 프론트엔드 > JS 튜토리얼 > NestJS 애플리케이션용 단위 테스트 및 Etest를 작성하는 방법

NestJS 애플리케이션용 단위 테스트 및 Etest를 작성하는 방법

Mary-Kate Olsen
풀어 주다: 2025-01-13 06:22:43
원래의
349명이 탐색했습니다.

소개

최근에는 NestJS 프로젝트에 대한 단위 테스트와 E2E 테스트를 작성하고 있습니다. 백엔드 프로젝트에 대한 테스트를 작성하는 것은 이번이 처음이며, 프런트엔드 테스트 경험과 프로세스가 다르기 때문에 시작하기가 어려웠습니다. 몇 가지 예를 살펴보니 테스트에 접근하는 방법에 대해 더 명확하게 이해하게 되었기 때문에 비슷한 혼란에 직면할 수 있는 다른 사람들에게 도움이 되도록 제가 배운 내용을 기록하고 공유하는 기사를 작성할 계획입니다.

또한 관련 유닛과 E2E 테스트가 완료된 데모 프로젝트를 준비했는데 관심이 있으실 것 같습니다. 코드는 Github(https://github.com/woai3c/nestjs-demo)에 업로드되었습니다.

단위 테스트와 E2E 테스트의 차이점

단위 테스트와 E2E 테스트는 소프트웨어 테스트 방법이지만 목표와 범위가 다릅니다.

단위 테스트에는 소프트웨어 내에서 테스트 가능한 가장 작은 단위를 확인하고 검증하는 작업이 포함됩니다. 예를 들어 함수나 메서드는 하나의 단위로 간주될 수 있습니다. 단위 테스트에서는 함수의 다양한 입력에 대해 예상되는 출력을 제공하고 해당 작업의 정확성을 검증합니다. 단위 테스트의 목표는 함수 내의 버그를 빠르게 식별하는 것이며 작성하기 쉽고 빠르게 실행됩니다.

반면 E2E 테스트는 전체 애플리케이션을 테스트하기 위해 실제 사용자 시나리오를 시뮬레이션하는 경우가 많습니다. 예를 들어 프런트엔드는 일반적으로 테스트를 위해 브라우저나 헤드리스 브라우저를 사용하는 반면 백엔드는 API 호출을 시뮬레이션하여 테스트합니다.

NestJS 프로젝트 내에서 단위 테스트는 사용자 모듈의 업데이트 메소드가 사용자를 올바르게 업데이트하는지 확인하는 등 특정 서비스 또는 컨트롤러의 메소드를 평가할 수 있습니다. 그러나 E2E 테스트에서는 새로운 사용자 생성부터 비밀번호 업데이트, 최종 사용자 삭제까지 여러 서비스와 컨트롤러가 관련된 전체 사용자 여정을 조사할 수 있습니다.

단위 테스트 작성

인터페이스를 포함하지 않는 유틸리티 함수나 메서드에 대한 단위 테스트를 작성하는 것은 비교적 간단합니다. 다양한 입력을 고려하고 해당 테스트 코드를 작성하기만 하면 됩니다. 그러나 인터페이스가 작동하게 되면 상황은 더욱 복잡해집니다. 코드를 예로 들어보겠습니다.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

위 코드는 auth.service.ts 파일의 verifyUser 메소드로, 주로 로그인 시 사용자가 입력한 사용자 이름과 비밀번호가 올바른지 확인하는 데 사용됩니다. 여기에는 다음 논리가 포함됩니다.

  1. 사용자 이름을 기준으로 사용자가 존재하는지 확인하세요. 그렇지 않은 경우 401 예외를 발생시킵니다(404 예외도 가능합니다).
  2. 사용자가 잠겨 있는지 확인하세요. 그렇다면 관련 메시지와 함께 401 예외를 발생시킵니다.
  3. 비밀번호를 암호화하고 데이터베이스의 비밀번호와 비교하세요. 정확하지 않은 경우 401 예외가 발생합니다(3회 연속 로그인 시도에 실패하면 5분 동안 계정이 잠깁니다).
  4. 로그인에 성공하면 이전에 실패한 로그인 시도 횟수를 모두 지우고(해당하는 경우) 사용자 ID와 사용자 이름을 다음 단계로 되돌립니다.

보시다시피, verifyUser 메소드에는 4가지 처리 로직이 포함되어 있으며 전체 유효성 검사 기능이 올바르게 작동하는지 확인하려면 이 4가지 지점에 해당하는 단위 테스트 코드를 작성해야 합니다.

첫 번째 테스트 사례

단위 테스트 작성을 시작할 때 문제에 직면합니다. findOne 메소드는 데이터베이스와 상호 작용해야 하며 사용자 이름을 통해 데이터베이스에서 해당 사용자를 찾습니다. 그러나 모든 단위 테스트가 데이터베이스와 상호 작용해야 한다면 테스트가 매우 번거로워질 것입니다. 따라서 이를 달성하기 위해 가짜 데이터를 모의할 수 있습니다.

예를 들어 woai3c라는 사용자를 등록했다고 가정해 보겠습니다. 그런 다음 로그인하는 동안 const 엔터티 = wait this.usersService.findOne({ 사용자 이름 });을 통해 verifyUser 메서드에서 사용자 데이터를 검색할 수 있습니다. 이 코드 줄이 원하는 데이터를 반환할 수 있는 한 데이터베이스 상호 작용이 없어도 문제가 없습니다. 우리는 모의 데이터를 통해 이를 달성할 수 있습니다. 이제 verifyUser 메소드에 대한 관련 테스트 코드를 살펴보겠습니다.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

userService의 findOne 메소드를 호출하여 사용자 데이터를 얻으므로 테스트 코드에서 usersService의 findOne 메소드를 모의해야 합니다.

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
로그인 후 복사
로그인 후 복사
로그인 후 복사

jest.fn()을 사용하여 실제 usersService.findOne()을 대체하는 함수를 반환합니다. usersService.findOne()이 지금 호출되면 반환 값이 없으므로 첫 번째 단위 테스트 사례가 통과됩니다.

beforeEach(async () => {
    usersService = {
      findOne: jest.fn(), // mock findOne method
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService, // real AuthService, because we are testing its methods
        {
          provide: UsersService, // use mock usersService instead of real usersService
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
로그인 후 복사
로그인 후 복사

const 엔터티의 findOne = wait this.usersService.findOne({ 사용자 이름 }); 유효성 검사 사용자 메서드가 반환 값이 없는 가짜 함수인 경우, 유효성 검사 사용자 메서드의 코드 2~4번째 줄이 실행될 수 있습니다.

it('should throw an UnauthorizedException if user is not found', async () => {
  await expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});
로그인 후 복사

예상대로 401 오류가 발생했습니다.

두 번째 테스트 사례

validateUser 메소드의 두 번째 논리는 다음과 같은 해당 코드를 사용하여 사용자가 잠겨 있는지 확인하는 것입니다.

if (!entity) {
  throw new UnauthorizedException('User not found');
}
로그인 후 복사

보시다시피, 사용자 데이터에 잠금 시간 lockUntil이 있고 잠금 종료 시간이 현재 시간보다 길면 현재 계정이 잠긴 것으로 판단할 수 있습니다. 따라서 lockUntil 필드를 사용하여 사용자 데이터를 모의해야 합니다.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

위의 테스트 코드에서는 필요한 lockUntil 필드를 포함하는 LockedUser 객체가 먼저 정의됩니다. 그런 다음 usersService.findOne.mockResolvedValueOnce(lockedUser);에 의해 달성된 findOne의 반환 값으로 사용됩니다. 따라서 verifyUser 메소드가 실행되면 그 안에 있는 사용자 데이터가 모의 데이터가 되어 두 번째 테스트 케이스를 성공적으로 통과할 수 있습니다.

단위 테스트 범위

유닛 테스트 커버리지(Code Coverage)는 애플리케이션 코드 중 얼마나 많은 부분이 유닛 테스트에 의해 커버되거나 테스트되었는지를 설명하는 데 사용되는 측정항목입니다. 일반적으로 테스트 케이스에 포함된 가능한 모든 코드 경로의 양을 나타내는 백분율로 표시됩니다.

단위 테스트 범위에는 일반적으로 다음 유형이 포함됩니다.

  • 라인 커버리지: 테스트에서 다루는 코드 라인 수
  • 함수 범위: 테스트에서 다루는 함수나 메소드의 수
  • 분기 적용 범위: 테스트에서 포함되는 코드 분기 수(예: if/else 문)
  • 명령문 적용 범위: 테스트에서 포함되는 코드의 명령문 수입니다.

단위 테스트 범위는 단위 테스트의 품질을 측정하는 중요한 지표이지만 유일한 지표는 아닙니다. 높은 커버리지율은 코드의 오류를 감지하는 데 도움이 될 수 있지만 코드의 품질을 보장하지는 않습니다. 낮은 적용률은 테스트되지 않은 코드가 있고 잠재적으로 감지되지 않은 오류가 있음을 의미할 수 있습니다.

아래 이미지는 데모 프로젝트의 단위 테스트 적용 범위 결과를 보여줍니다.

How to write unit tests and Etests for NestJS applications

서비스 및 컨트롤러와 같은 파일의 경우 일반적으로 더 높은 단위 테스트 적용 범위를 갖는 것이 더 좋지만, 모듈과 같은 파일의 경우 단위 테스트를 작성할 필요도 없고 작성할 수도 없습니다. 의미가 없기 때문입니다. 위 이미지는 전체 단위 테스트 범위에 대한 전체 측정항목을 나타냅니다. 특정 기능에 대한 테스트 적용 범위를 보려면 프로젝트의 루트 디렉터리에서 Coverage/lcov-report/index.html 파일을 열 수 있습니다. 예를 들어, verifyUser 메소드에 대한 특정 테스트 상황을 보고 싶습니다.

How to write unit tests and Etests for NestJS applications

보시다시피, verifyUser 메소드의 원래 단위 테스트 적용 범위는 100%가 아니며, 여전히 실행되지 않은 코드 두 줄이 있습니다. 하지만 4개의 ​​핵심 처리 노드에 영향을 주지 않으므로 크게 문제가 되지 않으며, 일차원적으로 높은 테스트 커버리지를 추구해서는 안 됩니다.

E2E 테스트 작성

단위 테스트에서는 모의 데이터를 사용하여 각 기능을 테스트할 수 있는지 확인하면서 verifyUser() 함수의 각 기능에 대한 단위 테스트를 작성하는 방법을 시연했습니다. e2E 테스트에서는 실제 사용자 시나리오를 시뮬레이션해야 하므로 테스트를 위해 데이터베이스에 연결하는 것이 필요합니다. 따라서 테스트할 auth.service.ts 모듈의 메소드는 모두 데이터베이스와 상호 작용합니다.

인증 모듈에는 기본적으로 다음 기능이 포함되어 있습니다.

  • 등록
  • 로그인
  • 토큰 새로고침
  • 사용자 정보 읽기
  • 비밀번호 변경
  • 사용자 삭제

E2E 테스트에서는 등록부터 사용자 삭제까지 이 6가지 기능을 하나씩 테스트해야 합니다. 테스트 중에 테스트를 수행할 전용 테스트 사용자를 생성한 다음 테스트 데이터베이스에 불필요한 정보가 남지 않도록 완료 시 이 테스트 사용자를 삭제할 수 있습니다.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

beforeAll 후크 기능은 모든 테스트가 시작되기 전에 실행되므로 여기에서 테스트 계정 TEST_USER_NAME을 등록할 수 있습니다. afterAll Hook 기능은 모든 테스트가 끝난 후 실행되므로 여기서 테스트 계정 TEST_USER_NAME을 삭제하는 것이 적합하며, 등록 및 삭제 기능도 테스트하기 편리합니다.

이전 섹션의 단위 테스트에서는 verifyUser 메서드를 중심으로 관련 단위 테스트를 작성했습니다. 실제로 이 메소드는 사용자의 계정과 비밀번호가 올바른지 확인하기 위해 로그인 중에 실행됩니다. 따라서 이번 e2E 테스트에서는 로그인 프로세스를 사용하여 e2E 테스트 케이스 구성 방법도 시연합니다.

전체 로그인 테스트 프로세스에는 5가지 작은 테스트가 포함됩니다.

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
로그인 후 복사
로그인 후 복사
로그인 후 복사

5가지 테스트는 다음과 같습니다.

  1. 로그인 성공, 200 반환
  2. 사용자가 존재하지 않으면 401 예외를 발생시킵니다
  3. 비밀번호나 사용자 이름이 제공되지 않으면 400 예외가 발생합니다
  4. 잘못된 비밀번호로 로그인하면 401 예외 발생
  5. 계정이 잠겨 있으면 401 예외를 발생시킵니다

이제 e2E 테스트 작성을 시작해 보겠습니다.

beforeEach(async () => {
    usersService = {
      findOne: jest.fn(), // mock findOne method
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService, // real AuthService, because we are testing its methods
        {
          provide: UsersService, // use mock usersService instead of real usersService
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
로그인 후 복사
로그인 후 복사

e2E 테스트 코드 작성은 비교적 간단합니다. 간단히 인터페이스를 호출한 다음 결과를 확인하면 됩니다. 예를 들어 성공적인 로그인 테스트를 위해서는 반환된 결과가 200인지 확인하기만 하면 됩니다.

처음 네 가지 테스트는 매우 간단합니다. 이제 계정이 잠겨 있는지 확인하는 좀 더 복잡한 e2E 테스트를 살펴보겠습니다.

async validateUser(
  username: string,
  password: string,
): Promise<UserAccountDto> {
  const entity = await this.usersService.findOne({ username });
  if (!entity) {
    throw new UnauthorizedException('User not found');
  }
  if (entity.lockUntil && entity.lockUntil > Date.now()) {
    const diffInSeconds = Math.round((entity.lockUntil - Date.now()) / 1000);
    let message = `The account is locked. Please try again in ${diffInSeconds} seconds.`;
    if (diffInSeconds > 60) {
      const diffInMinutes = Math.round(diffInSeconds / 60);
      message = `The account is locked. Please try again in ${diffInMinutes} minutes.`;
    }
    throw new UnauthorizedException(message);
  }
  const passwordMatch = bcrypt.compareSync(password, entity.password);
  if (!passwordMatch) {
    // $inc update to increase failedLoginAttempts
    const update = {
      $inc: { failedLoginAttempts: 1 },
    };
    // lock account when the third try is failed
    if (entity.failedLoginAttempts + 1 >= 3) {
      // $set update to lock the account for 5 minutes
      update['$set'] = { lockUntil: Date.now() + 5 * 60 * 1000 };
    }
    await this.usersService.update(entity._id, update);
    throw new UnauthorizedException('Invalid password');
  }
  // if validation is sucessful, then reset failedLoginAttempts and lockUntil
  if (
    entity.failedLoginAttempts > 0 ||
    (entity.lockUntil && entity.lockUntil > Date.now())
  ) {
    await this.usersService.update(entity._id, {
      $set: { failedLoginAttempts: 0, lockUntil: null },
    });
  }
  return { userId: entity._id, username } as UserAccountDto;
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

3회 연속 로그인 실패 시 계정이 잠깁니다. 따라서 이 테스트에서는 테스트 계정 TEST_USER_NAME을 사용할 수 없습니다. 테스트가 성공하면 이 계정이 잠기고 다음 테스트를 계속할 수 없기 때문입니다. 계정 잠금을 테스트하기 위해 특별히 다른 새 사용자 TEST_USER_NAME2을 등록하고 테스트가 성공한 후 이 사용자를 삭제해야 합니다. 보시다시피 이 e2E 테스트의 코드는 상당히 커서 많은 설정 및 해제 작업이 필요하지만 실제 테스트 코드는 다음 몇 줄에 불과합니다.

import { Test } from '@nestjs/testing';
import { AuthService } from '@/modules/auth/auth.service';
import { UsersService } from '@/modules/users/users.service';
import { UnauthorizedException } from '@nestjs/common';
import { TEST_USER_NAME, TEST_USER_PASSWORD } from '@tests/constants';
describe('AuthService', () => {
  let authService: AuthService; // Use the actual AuthService type
  let usersService: Partial<Record<keyof UsersService, jest.Mock>>;
  beforeEach(async () => {
    usersService = {
      findOne: jest.fn(),
    };
    const module = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = module.get<AuthService>(AuthService);
  });
  describe('validateUser', () => {
    it('should throw an UnauthorizedException if user is not found', async () => {
      await expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    // other tests...
  });
});
로그인 후 복사
로그인 후 복사
로그인 후 복사

e2E 테스트 코드 작성은 비교적 간단합니다. 모의 데이터나 테스트 범위를 고려할 필요가 없습니다. 전체 시스템 프로세스가 예상대로 실행되면 충분합니다.

테스트 작성 여부

가능하다면 일반적으로 테스트 작성을 권장합니다. 이렇게 하면 시스템의 견고성, 유지 관리성 및 개발 효율성이 향상될 수 있습니다.

시스템 견고성 강화

코드를 작성할 때 우리는 일반적으로 핵심 기능이 제대로 작동하는지 확인하기 위해 일반 입력에서의 프로그램 흐름에 중점을 둡니다. 그러나 비정상적인 입력과 같은 일부 극단적인 경우를 종종 간과할 수 있습니다. 테스트 작성이 이를 변경합니다. 이러한 경우를 처리하고 적절하게 대응하는 방법을 고려하여 충돌을 방지해야 합니다. 테스트 작성은 시스템 견고성을 간접적으로 향상시킨다고 할 수 있습니다.

유지보수성 강화

포괄적인 테스트가 포함된 새로운 프로젝트를 맡는 것은 매우 즐거울 수 있습니다. 가이드 역할을 하여 다양한 기능을 빠르게 이해할 수 있도록 도와줍니다. 함수의 코드를 한줄 한줄 일일이 살펴보지 않고도 테스트 코드만 봐도 각 함수의 예상되는 동작과 경계조건을 쉽게 파악할 수 있습니다.

개발 효율성 향상

한동안 업데이트되지 않은 프로젝트가 갑자기 새로운 요구 사항을 받았다고 상상해 보세요. 변경한 후에는 버그 발생에 대해 걱정할 수 있습니다. 테스트가 없으면 전체 프로젝트를 수동으로 다시 테스트해야 하므로 시간이 낭비되고 비효율적입니다. 전체 테스트를 통해 단일 명령으로 코드 변경이 기존 기능에 영향을 미쳤는지 여부를 알 수 있습니다. 오류가 있어도 빠르게 찾아서 해결할 수 있습니다.

언제 테스트를 작성하지 말아야 합니까?

단기 프로젝트요구사항 반복이 매우 빠른 프로젝트의 경우 테스트를 작성하지 않는 것이 좋습니다. 예를 들어, 이벤트가 끝난 후에는 쓸모가 없게 될 이벤트를 위한 일부 프로젝트에는 테스트가 필요하지 않습니다. 또한 매우 빠른 요구사항 반복을 겪는 프로젝트의 경우 테스트 작성이 개발 효율성을 높일 수 있다고 말했지만 이는 함수 반복이 느리다는 전제에 기반한 것입니다. 방금 완료한 기능이 하루 이틀 만에 변경되면 관련 테스트 코드를 다시 작성해야 합니다. 따라서 테스트 작성은 시간이 많이 걸리고 노력할 가치가 없기 때문에 테스트를 전혀 작성하지 않고 테스트 팀에 의존하는 것이 좋습니다.

결론

NestJS 프로젝트에 대한 단위 테스트 및 e2E 테스트 작성 방법을 자세히 설명한 후에도 테스트의 중요성을 다시 한번 강조하고 싶습니다. 이는 시스템의 견고성, 유지 관리성 및 개발 효율성을 향상시킬 수 있습니다. 테스트를 작성할 기회가 없다면 직접 연습 프로젝트를 시작하거나 일부 오픈 소스 프로젝트에 참여하여 코드를 기여하는 것이 좋습니다. 오픈 소스 프로젝트에는 일반적으로 코드 요구 사항이 더 엄격합니다. 코드에 기여하려면 새로운 테스트 사례를 작성하거나 기존 테스트 사례를 수정해야 할 수도 있습니다.

참고 자료

  • NestJS: 효율적이고 확장 가능한 Node.js 서버측 애플리케이션을 구축하기 위한 프레임워크입니다.
  • MongoDB: 데이터 저장에 사용되는 NoSQL 데이터베이스입니다.
  • Jest: JavaScript 및 TypeScript용 테스트 프레임워크입니다.
  • Supertest: HTTP 서버를 테스트하기 위한 라이브러리입니다.

위 내용은 NestJS 애플리케이션용 단위 테스트 및 Etest를 작성하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿