Nest.js의 프로바이더 사용 가이드

이상문
8 min readMay 10, 2023

Nest.js는 Node.js를 사용하여 확장 가능하고 유지 관리 가능한 웹 애플리케이션을 구축하기 위한 강력한 프레임워크이다. Nest.js의 주요 기능 중 하나는 애플리케이션의 여러 부분 간의 종속성을 쉽게 관리할 수 있는 종속성 주입 기능이다. 이 글에서는 Nest.js의 핵심 요소 중 하나인 프로바이더에 대해 살펴보고 이를 사용하여 더 나은 모듈식 애플리케이션을 구축하는 방법을 정리한다.

프로바이더란 무엇인가

Nest.js에서 프로바이더는 종속성 주입을 통해 다른 클래스에 서비스를 제공하는 객체이다. 프로바이더는 애플리케이션의 여러 부분에서 객체의 생성 및 공유를 관리하는 데 사용할 수 있다. 예를 들어, 프로바이더를 사용하여 데이터베이스 연결을 관리하거나 타사 API에 대한 접근을 할 수 있다.

공급자는 필요에 따라 다양한 방식으로 정의할 수 있다. 몇 가지 타입을 소개한다.

  • 클래스 공급자: 클래스를 사용하여 정의되는 공급자이다. 예를 들어 DatabaseService 클래스를 사용하여 데이터베이스 연결에 대한 공급자를 정의한다.
  • 값(value) 공급자: 특정 값을 사용하여 정의되는 공급자이다. 예를 들어 문자열 또는 숫자 값을 사용하여 구성 설정에 대한 공급자를 정의한다.
  • 팩토리 공급자: 팩토리 함수를 사용하여 정의되는 공급자이다. 예를 들어 createLogger 팩토리 함수를 사용하여 로거에 대한 공급자를 정의할 수 있다.
  • 별칭(alias) 공급자: 다른 공급자의 별칭으로 정의되는 공급자이다. 예를 들어 기존 LoggerService 공급자를 단순히 참조하는 로거에 대한 별칭 공급자를 정의할 수 있다.

모듈에 공급자 등록하기

프로바이더를 사용하려면 먼저 모듈에 등록해야 한다. Nest.js에서 모듈은 특정 기능 또는 기능 집합을 제공하기 위해 함께 작동하는 관련 프로바이더, 컨트롤러 및 기타 컴포넌트를 모아놓은 것이다. 모듈에 프로바이더를 등록하려면 모듈 정의의 providers 속성을 사용하면 된다.

import { Module } from '@nestjs/common';
import { DatabaseService } from './database.service';

@Module({
providers: [DatabaseService],
})
export class AppModule {}

앞의 예제에서는 AppModule이라는 모듈을 정의하고 이 모듈에 DatabaseService 클래스 프로바이더를 등록한다. 이제 이 모듈의 일부인 애플리케이션의 다른 부분에서도 이 공급자를 사용할 수 있다.

사용자 지정 프로바이더 만들기

앞서 설명한 기본 프로바이더 타입 외에도 필요에 따라 사용자 정의 프로바이더를 만들 수도 있다. 간단한 로거 서비스를 위한 사용자 정의 프로바이더를 만드는 방법을 살펴보자.

먼저 로거 서비스에 대한 새 프로바이더 클래스를 생성한다. 이 클래스에는 메시지를 로깅하는 메서드가 있어야 한다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerService {
log(message: string) {
console.log(`[LoggerService] ${message}`);
}
}

다음으로 @Module 데코레이터를 사용하여 프로바이더를 모듈에 등록한다. 이는 providers 배열에 프로바이더를 추가하여 수행할 수 있다.

import { Module } from '@nestjs/common';
import { LoggerService } from './logger.service';

@Module({
providers: [LoggerService],
})
export class AppModule {}

이제 로깅 서비스가 필요한 다른 클래스에 LoggerService를 삽입할 수 있다.

import { Injectable } from '@nestjs/common';
import { LoggerService } from './logger.service';

@Injectable()
export class MyService {
constructor(private readonly logger: LoggerService) {}

doSomething() {
this.logger.log('Doing something...');
}
}

비동기 프로바이더

Nest.js에서는 useValue 대신 useFactory 프로퍼티를 사용하여 비동기 프로바이더를 사용할 수 있다. 이를 통해 주입된 종속성 사용을 포함하여 프로바이더를 동적으로 생성할 수 있다.

비동기 프로바이더를 생성하려면 제공하려는 값의 프로미스를 반환하는 팩토리 함수를 정의해야 한다.

import * as fs from 'fs';
import { ConfigService } from './config.service';

export const configProvider = {
provide: ConfigService,
useFactory: async () => {
const filePath = process.env.CONFIG_FILE_PATH;
const fileContents = await fs.promises.readFile(filePath, 'utf8');
return new ConfigService(JSON.parse(fileContents));
},
};

위의 예제에서 환경 설정 서비스를 제공하는 configProvider라는 객체를 정의한다. useValue를 사용하는 대신 useFactory를 사용하고 구성 파일을 읽고, 그 내용을 구문 분석하고, 구문 분석된 내용이 포함된 새 ConfigService 인스턴스를 반환하는 비동기 함수를 정의한다.

테스트에서 mocking 프로바이더 사용

Nest.js에서 프로바이더를 사용하면 얻을 수 있는 이점 중 하나는 테스트에서 프로바이더를 쉽게 mocking할 수 있다는 것이다. 이를 통해 종속이 되는 객체의 모의 버전을 제공하여 클래스를 분리하여 테스트할 수 있다.

이를 설명하기 위해 MailerService에 종속되는 UserService 클래스를 고려해 보자.

import { Injectable } from '@nestjs/common';
import { MailerService } from './mailer.service';

@Injectable()
export class UserService {
constructor(private readonly mailerService: MailerService) {}

async sendWelcomeEmail(email: string) {
await this.mailerService.sendMail(email, 'Welcome to our app!');
}
}

UserService 클래스를 테스트하기 위해 테스트에서 제어할 수 있는 MailerService 의 모의 버전을 사용한다.

import { Test } from '@nestjs/testing';
import { UserService } from './user.service';
import { MailerService } from './mailer.service';

describe('UserService', () => {
let userService: UserService;
let mailerServiceMock: MailerService;

beforeEach(async () => {
mailerServiceMock = {
sendMail: jest.fn(),
} as unknown as MailerService;

const moduleRef = await Test.createTestingModule({
providers: [
UserService,
{
provide: MailerService,
useValue: mailerServiceMock,
},
],
}).compile();

userService = moduleRef.get<UserService>(UserService);
});

describe('sendWelcomeEmail', () => {
it('should call mailerService.sendMail with the correct arguments', async () => {
await userService.sendWelcomeEmail('test@example.com');

expect(mailerServiceMock.sendMail).toHaveBeenCalledWith(
'test@example.com',
'Welcome to our app!',
);
});
});
});

앞의 예제에서는 Jest의 jest.fn() 함수를 사용하여 MailerService의 모의 버전을 정의한다. 이후 테스트 모듈을 생성하고 useValue 프로퍼티를 사용하여 UserService와 모의 MailerService를 넘긴다. 그런 다음 moduleRef.get<UserService>(UserService)를 사용하여 UserService 인스턴스를 검색하고 테스트에 사용한다.

마무리

이 글에서는 Nest.js의 프로바이더 개념과 프로바이더 타입을 이야기하고, 프로바이더를 생성하는 방법과 테스트에서 효율적으로 사용하는 방법에 대해서 정리했다.

--

--

이상문

software developer working mainly in field of streaming, using C++, javascript