Skip to content

Nest Test Code

top-chaser edited this page Dec 4, 2023 · 1 revision

설치

$ npm i --save-dev @nestjs/testing

Unit Testing.

Testing files should have a .spec or .test suffix.

isolated testing

  • 프레임워크로부터 독립적
  • dependency injection 가 없다
import { CatsController } from "./cats.controller";
import { CatsService } from "./cats.service";

describe("CatsController", () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(() => {
    catsService = new CatsService();
    catsController = new CatsController(catsService);
  });

  describe("findAll", () => {
    it("should return an array of cats", async () => {
      const result = ["test"];
      jest.spyOn(catsService, "findAll").mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

Testing Utilities

@nestjs/testing : 강력한 테스트 프로세스를 가능하게 함

import { Test } from "@nestjs/testing"; //
import { CatsController } from "./cats.controller";
import { CatsService } from "./cats.service";

describe("CatsController", () => {
  let catsController: CatsController;
  let catsService: CatsService;

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

    catsService = moduleRef.get<CatsService>(CatsService);
    catsController = moduleRef.get<CatsController>(CatsController);
  });

  describe("findAll", () => {
    it("should return an array of cats", async () => {
      const result = ["test"];
      jest.spyOn(catsService, "findAll").mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

Test

  • 전체 Nest 런타임을 moke하는 애플리케이션 실행 컨텍스트를 제공하는 데 유용합니다.
  • moke 및 overriding을 포함하여 클래스 인스턴스를 쉽게 관리할 수 있는 후크를 제공합니다.
  • createTestingModule() :
    • 모듈 metadata object를 인수로 사용합니다. (@Module() 데코레이터에 전달한 것과 동일한 object).
    • TestingModule 인스턴스를 반환합니다.
  • compile() :
    • 이 방법은 종속성이 있는 모듈을 bootstrap 합니다. (NestFactory.create()를 사용하여 기존 main.ts 파일에서 애플리케이션을 부트스트랩하는 방식과 유사).
    • 테스트 준비가 된 모듈을 반환합니다.
    • asynchronous ⇒ await 반환
    • 모듈이 컴파일되면 get() 메서드를 사용하여 선언된 정적 인스턴스(컨트롤러 및 공급자)를 검색할 수 있습니다.

Auto moking - 공부 필요

  • 많은 의존성이 있을 때 유용
  • 이 기능을 사용하려면 createTestingModule()을 useMocker() 메서드와 연결하여 종속성 모의 객체에 대한 팩토리를 전달해야 합니다.
    • 이 팩토리는 인스턴스 토큰인 선택적 토큰, Nest 공급자에게 유효한 모든 토큰을 가져와 모의 구현을 반환할 수 있습니다. 아래는 jest-mock을 사용하여 일반 모커를 생성하고 jest.fn()을 사용하여 CatsService에 대한 특정 모커를 생성하는 예입니다.
import { ModuleMocker, MockFunctionMetadata } from "jest-mock";

const moduleMocker = new ModuleMocker(global);

describe("CatsController", () => {
  let controller: CatsController;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [CatsController],
    })
      .useMocker((token) => {
        const results = ["test1", "test2"];
        if (token === CatsService) {
          return { findAll: jest.fn().mockResolvedValue(results) };
        }
        if (typeof token === "function") {
          const mockMetadata = moduleMocker.getMetadata(
            token
          ) as MockFunctionMetadata<any, any>;
          const Mock = moduleMocker.generateFromMetadata(mockMetadata);
          return new Mock();
        }
      })
      .compile();

    controller = moduleRef.get(CatsController);
  });
});

**End-to-end testing**

  • 보다 종합적인 수준에서 클래스와 모듈의 상호 작용 Test
  • end User ←→ production
import * as request from "supertest";
import { Test } from "@nestjs/testing";
import { CatsModule } from "../../src/cats/cats.module";
import { CatsService } from "../../src/cats/cats.service";
import { INestApplication } from "@nestjs/common";

describe("Cats", () => {
  let app: INestApplication;
  let catsService = { findAll: () => ["test"] };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [CatsModule],
    })
      .overrideProvider(CatsService)
      .useValue(catsService)
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer()).get("/cats").expect(200).expect({
      data: catsService.findAll(),
    });
  });

  afterAll(async () => {
    await app.close();
  });
});

**NestJs TypeORM 테스트 코드**

Fixtures

  • Fixture란
    • Test Fixture : 고정되어 있는 물체 를 의미
    • 테스트 실행을 위해 베이스라인으로서 사용되는 객체들의 고정된 상태
    • 테스트를 위한 기본으로 세팅된 데이터 구성
beforeEach(() => {
	setFixtures(...);
});

테스트 코드를 실행할 때 beforeEach를 통해 테스트 코드가 실행되기 전 작업을 명시할 수 있습니다. beforeEach에서 setFixtures 함수를 구성해 줌으로써 테스트케이스가 실행되기 전 정해둔 데이터로 db를 초기화할 수 있습니다. 그럼 모든 테스트는 동일한 데이터를 가지고 테스트 케이스가 실행될 수 있습니다.

그리고 테스트가 끝난 후 afterEach에서 사용한 데이터를 싹 날려줍니다.

afterEach(() => {
  truncate all tables..
})

Ex)

// test.spec.ts
setFixtures([[FooRepository, FooEntities]]);

// setFixtures 내부
// beforeEach
await repository.runTransaction(async (tx) => {
  await repository.rawQuery('set foreign_key_checks = 0', [], tx);
  await repository.insert(JSON.parse(JSON.stringify(data)), tx);
  await repository.rawQuery('set foreign_key_checks = 1', [], tx);
});

// afterEach
await repository.runTransaction(async (tx) => {
    await repository.rawQuery('set foreign_key_checks = 0', [], tx);
    await repository.rawQuery(`truncate ${repository.getTableName()}`, [], tx);
    await repository.rawQuery('set foreign_key_checks = 1', [], tx);
  });
}

**Docker를 이용한 로컬 mysql 설치**

  • 왜 local에 mysql을 설치하지 않나요?
  • 왜 dev서버에 db로 연결하지 않나요?

우선 꼭 docker가 아니어도 됩니다. 로컬에 mysql을 직접 설치해도 됩니다. 다만 테스트코드를 실행하는 자는 꼭 mysql의 설치에 익숙한 백엔드 개발자가 아닐 수 있음에 주의해야 합니다.

프론트엔드 개발자가 테스트코드를 실행할 경우가 있고, 그런경우라면 프론트엔드 개발자도 mysql설치를 해야합니다.

보통 E2E라고 하면 controller <—> db 까지의 테스트라고 생각할 수 있지만, 좀 더 크게 E2E를 본다면 프론트엔드 <—> 백엔드까지의 범위라고 볼 수 있습니다.

그런경우 프론트에서 특정 버턴을 누르고 api 가 호출되어 db에 데이터가 들어가는 것 까지 테스트할 수 있습니다.

(js에서 사용하는 도구로는 cypress와 같은게 있습니다.)

test 할 때 자동으로 docker test database 만들기

server/api-server/testMysql에 dockerfile 생성

FROM mysql:8.2.0

ENV MYSQL_ROOT_PASSWORD=audgml145
ENV MYSQL_DATABASE=gbs
ENV LANG=C.UTF-8

COPY ./docker/mysql/init-test-db.sql /docker-entrypoint-initdb.d/init-test-db.sql
  • yarn test:docker : docker 자동 생성 및 test 실행
  • cp ./sql/schema.sql ./testMysql/docker/mysql/init-test-db.sq : 실제 db sql을 test db의 초기 sql로 복사
  • docker build -t testdb ./testMysql : docker image 생성
  • docker run -it -p 3306:3306 testdb : 컨테이너 생성 시 -p 옵션을 이용해서 바인딩할 포트를 부여한다.
  • jest --watch : test 실행

Ref

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

https://kyungyeon.dev/posts/85

Clone this wiki locally