728x90

nestjs에서 jest를 이용한 End2End 테스트 케이스를 작성하였습니다.

src/user/user.controller.spec.ts 파일에 사용자 추가/수정/삭제/조회에 대한 테스트 케이스를 작성하였습니다.

  1. End2End 데이터 기준의 validate 테스트
  2. 로그인 Mock 객체 주입 테스트
  3. repository Mock 객체 테스트

전체 테스트 케이스 실행은 다음의 명령어로 할 수 있습니다.

node run test

특정 테스트 케이스 실행은 다음 명령어로 할 수 있습니다.

node node_modules/jest/bin/jest.js src/user/user.controller.spec.ts 

테스트 케이스를 이용하면 코드의 오류를 빠르게 찾아낼 수 있습니다.
그 외에도 플로우차트 기반으로 테스트 케이스를 작성하여 명세에 알맞은 프로그램을 작성할 수도 있고요!

전체 코드 바로 가기 : https://github.com/lahuman/nestjs_101

Posted by lahuman

댓글을 달아 주세요

728x90

자바라 쓰고 Springframework를 공부한다.

Spring의 주요 3가지 컨셉은 아래와 같습니다.

1) IOC : 제어의 역전 / 의존성 주입
2) AOP : 관심의 분리
3) PSA : 일관성 있는 추상화

그럼 실제로 어떻게 사용될까?

IOC : 제어의 역전 / 의존성 주입 사용 예제

Bean은 IoC 컨테이너 안에 등록된 객체들을 의미 합니다.
모든 클래스의 객체가 Bean으로 등록되지 않습니다. @Repository, @Component, @Service, @Bean 등의 어노테이션을 통해서 Bean으로 등록 가능합니다.

사용 예로는 @Autowired 어노테이션을 이용해서 Bean을 주입할 수 있습니다.
Bean 주입이 주는 이점은, 객체의 관리를 스프링 컨테이너가 하기에 개발자가 언제 빈을 생성하고 소멸시킬지 신경쓰지 않아도 됩니다.

AOP : 관심의 분리 예제

가장 큰 예로 @Transctional을 이야기 할 수 있습니다.
connection에 대하여 rollback, commit등을 신경쓰지 않고 처리 하게 됩니다.
결국 트렌젝션 처리는 위임하고 구현 기능에 집중하는 코드를 작성하면 됩니다.

PSA : 일관성 있는 추상화

Service Abstraction으로 제공되는 기술을 다른 기술 스택으로 간편하게 바꿀 수 있는 확장성을 갖고 있는 것이 Portable Service Abstraction. 줄여서 PSA라고 합니다.
예로는 서블릿을 직접 사용하는 것이 아니라, Controller의 @GetMapping이나 @PostMapping을 통해 특정 url로 요청이 들어왔을 때, 해당 블록이 요청을 처리하도록 구현 되어 있습니다.
이렇게 추상화 계층을 사용해 어떤 기술을 내부에 숨기고 개발자에게 편의성을 제공하는 것을 Service Abstraction이라고 합니다.

스프링은 MVC라는 추상화 기법을 사용. Spring Web MVC를 사용하면 서블릿을 직접 구현할 필요가 없습니다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

728x90

오늘 설명은 Controller 기반의 테스트에서 다음 내용입니다.

  1. Repository 객체를 Mock으로 주입
  2. Connection 객체를 Mock으로 주입
  3. mockAuthGuard 객체를 Mock으로 주입하여 로그인 처리
  4. 첨부파일 업로드

Mock 객체 생성

자동으로 만들어진 테스트 케이스는 아래와 같습니다

import { Test, TestingModule } from '@nestjs/testing';
import { Controller } from './my.controller';

describe('Controller', () => {
  let controller: Controller;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [Controller],
    }).compile();

    controller = module.get<Controller>(Controller);
  });

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

Controller의 소스는 기본적으로 service를 호출하고, service에서는 Entity를 이용하여 Repository를 Inject 합니다.

Inject에 필요한 Mock객체들을 생성을 합니다.
내부 구현이 필요 없을 경우 jest.fn()를 활용하면 쉽게 처리 할 수 있습니다.

class MockRepository {

  async save(any) {
    return new OssAtachFileDEntity({
      "refSeq": 1,
      "refCd": "RE",
      "regrId": "12345",
      "modrId": "12345",
      "fileName": "첨부파일 테스트.pdf",
      "filePath": "202106/e55daf47-af60-4e8d-9e54-67cbfc680556",
      "size": 73666,
      "fileExtension": "pdf",
      "seq": 2
    });
  }
  async find() {
    return [new OssAtachFileDEntity({
      "refSeq": 1,
      "refCd": "RE",
      "regrId": "12345",
      "modrId": "12345",
      "fileName": "첨부파일 테스트.pdf",
      "filePath": "202106/e55daf47-af60-4e8d-9e54-67cbfc680556",
      "size": 73666,
      "fileExtension": "pdf",
      "seq": 2
    })];
  }
  async findOne(any) {
    return new OssAtachFileDEntity({
      "refSeq": 1,
      "refCd": "RE",
      "regrId": "12345",
      "modrId": "12345",
      "fileName": "첨부파일 테스트.pdf",
      "filePath": "202106/e55daf47-af60-4e8d-9e54-67cbfc680556",
      "size": 73666,
      "fileExtension": "pdf",
      "seq": 2
    });
  }

  async remove() {

  }
}

const mockConnection = () => ({
  transaction: jest.fn(),
  createQueryRunner: () => ({
    connect: jest.fn(),
    startTransaction: jest.fn(),
    commitTransaction: jest.fn(),
    rollbackTransaction: jest.fn(),
    release: jest.fn(),
    manager: {
      save: (r => r)
    }
  })
});

const mockAuthGuard: CanActivate = {
  canActivate: (context: ExecutionContext) => {
    const request = context.switchToHttp().getRequest();
    request.user = {
      id: '12345',
      name: "임광규",
      email: 'lahuman@daum.net'
    };
    return request.user;
  }
};

Mock 객체 주입

구현한 Mock 객체들을 module에 Inject 처리를 합니다.

 let app: INestApplication;
  let httpService: HttpService;
  let controller: Controller;

  beforeEach(async () => {

    const module: TestingModule = await Test.createTestingModule({
      imports: [HttpModule, ConfigModule.forRoot({ isGlobal: true })],
      controllers: [Controller],
      providers: [Service,
        {
          provide: getRepositoryToken(OssReqMEntity),
          useClass: MockRepository,
        },
        {
          provide: Connection,
          useFactory: mockConnection
        }
      ],
    })
      .overrideGuard(AuthenticatedGuard).useValue(mockAuthGuard)
      .compile();
    app = module.createNestApplication();
    httpService = module.get<HttpService>(HttpService);
    await app.init();

    controller = module.get<Controller>(Controller);
  });

첨부파일 테스트

 it('첨부파일 추가', async () => {
    const response = await request(app.getHttpServer())
      .post("/attach-file/upload")
      .attach('file', '/path/file-name')
      .expect(201);
    expect(JSON.parse(response.text).status).toEqual(true);
  });

로그인 처리 테스트

로그인의 경우 PASSPORT-SAML 방식을 이용하였으며 Request에 user라는 객체를 이용합니다.

등록된 결과에 userId가 12345인지 확인하는 테스트를 진행합니다.

 it('조회', async () => {
    const response = await request(app.getHttpServer())
      .get('/1')
      .expect(200);
    expect(JSON.parse(response.text).regrId).toEqual("12345");
  });

이상으로 기본적인 Nestjs 테스트 케이스를 사용해보았습니다.

은근 어려웠네요.

Posted by lahuman

댓글을 달아 주세요