Skip to content

๐Ÿ“„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์™€ ๋ชจํ‚น

Junsang Yu edited this page Dec 5, 2024 · 1 revision

ํ˜„์žฌ ์ƒํ™ฉ

๊ธฐ์กด์—๋Š” In-memory๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ์—ˆ์œผ๋‚˜, ํ˜„์žฌ๋Š” MySQL๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์˜€์Œ

๊ทธ๋Ÿฌ๋‚˜ ์•„์ง mergeํ•˜์ง€ ์•Š์•˜๊ณ , ์ด๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์–ด๋ ค์šด ์ƒํƒœ์ด๋‹ค.

  • local์— ์žˆ๋Š” ํ”„๋ก ํŠธ ์ฝ”๋“œ๋กœ ๊ฒ€์ฆ : ํ˜„์žฌ๋Š” random-name api์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ๊ฒ€์ฆํ•˜๊ธฐ ํž˜๋“  ์ƒํ™ฉ
  • postman์œผ๋กœ ๊ฒ€์ฆ : ์ˆ˜๋™์œผ๋กœ ํ™•์ธํ•ด์•ผ ํ•จ

๊ทธ๋ž˜์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž unit test ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๊ณ  ํ•œ๋‹ค.


ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

์‹ค์ œ DB์— ๊ฐ’์ด ์ €์žฅ๋˜๋ฉด ์•ˆ ๋˜๋ฏ€๋กœ, Mock DB๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋„์ปค๋ฅผ ํ†ตํ•ด Testcontainer๋ฅผ ๋„์šฐ๊ณ  ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์œผ๋‚˜ ์‹œ๊ฐ„ ๊ด€๊ณ„์ƒ ํŒจ์Šคํ•œ๋‹ค.

๊ธฐ๋ณธ์ ์ธ ์ปจ์…‰ : ๋น„๊ต๊ตฐ(StudyRoomRepository)์™€ ๋Œ€์กฐ๊ตฐ(Repository)๋ฅผ ๋‘์–ด ์ด๋ฅผ ๋น„๊ตํ•œ๋‹ค.

// ๋น„๊ต๊ตฐ
let studyRoomRepository: StudyRoomRepository;

// ๋Œ€์กฐ๊ตฐ
let repository: Repository<StudyRoom>;

1. Custom Mock Repository ์ฃผ์ž…

ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „

  1. Test.createTestingModule์„ ํ†ตํ•ด ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์„ ์„ค์ •ํ•œ๋‹ค.
  2. providers๋ฅผ ํ†ตํ•ด ๋น„๊ต๊ตฐ๊ณผ ๋Œ€์กฐ๊ตฐ์˜ ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•œ๋‹ค.
    • ๋น„๊ต๊ตฐ : ๊ทธ๋Œ€๋กœ ๋„ฃ๋Š”๋‹ค.
    • ๋Œ€์กฐ๊ตฐ : ์‹ค์ œ DB๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋˜๋ฏ€๋กœ, ๊ธฐ๋ณธ Repository์™€ StudyRoom entity์— ๋Œ€ํ•œ ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•œ๋‹ค.
      • getRepositoryToken : TypeORM์—์„œ ํŠน์ •ํ•œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ ํ† ํฐ์„ ์˜๋ฏธํ•œ๋‹ค. ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ๋Š” StudyRoomRepository๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์™€ ๋Œ€์‘ํ•˜๋Š” StudyRoom์— ๋Œ€ํ•œ ํ† ํฐ์„ ์‚ฌ์šฉํ•œ๋‹ค.
      • useClass : TypeORM์—์„œ ์–ด๋–ค class(ํ˜„์žฌ ํ…Œ์ŠคํŠธ์—์„œ๋Š” repository์— ํ•ด๋‹นํ•œ๋‹ค)๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€ ์„ค์ •ํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
  3. compile์„ ํ†ตํ•ด ์„ค์ •ํ•œ ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์„ ์ƒ์„ฑํ•˜๊ณ , ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ๋ชจ๋‘ ์ฃผ์ž…ํ•œ๋‹ค.
  4. ์œ„์—์„œ ์ƒ์„ฑ๋œ ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ์„ ์‹ค์ œ repository์— ์ฃผ์ž…ํ•œ๋‹ค.
let studyRoomRepository: StudyRoomRepository;
let mockRepository: jest.Mocked<Repository<StudyRoom>>;

beforeEach(async () => {
	const module: TestingModule = await Test.createTestingModule({
		providers: [
	    StudyRoomRepository,
	    {
		    provide: getRepositoryToken(StudyRoom),
	      useValue: {
		      create: jest.fn(),
		      save: jest.fn(),
	        findOne: jest.fn(),
	      },
	    },
	  ],
  }).compile();

	studyRoomRepository = module.get<StudyRoomRepository>(StudyRoomRepository);
	mockRepository = module.get<Repository<StudyRoom>>(
		getRepositoryToken(StudyRoom),
	) as jest.Mocked<Repository<StudyRoom>>;
});
  • ์žฅ์ 
    • DB ์—ฐ๊ฒฐ ์—†์ด ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
    • ์™ธ๋ถ€ ์ข…์†์„ฑ์„ ์ตœ์†Œํ™”ํ•œ๋‹ค.
  • ๋‹จ์ 
    • ์‹ค์ œ DB ๋™์ž‘์„ ๊ฒ€์ฆํ•˜์ง€๋Š” ์•Š๊ณ , ๋‹จ์ˆœ ์˜์กด์„ฑ ์ฃผ์ž…์šฉ์ด๋‹ค.
    • ํŠน์ • ๋ฐ˜ํ™˜๊ฐ’์„ ์›ํ•œ๋‹ค๋ฉด mock.fn์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.
  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์ ํ•ฉํ•˜๋‹ค.

2. In-Memory DB Repository ์ฃผ์ž…

beforeAll(async () => {
	const module: TestingModule = await Test.createTestingModule({
		imports: [
			TypeOrmModule.forRoot({
				type: 'sqlite',
				database: ':memory:', // ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ
        entities: [StudyRoom], // ์—”ํ‹ฐํ‹ฐ ๋“ฑ๋ก
        synchronize: true, // ์Šคํ‚ค๋งˆ ๋™๊ธฐํ™”
      }),
      TypeOrmModule.forFeature([StudyRoomRepository]), // ํ…Œ์ŠคํŠธํ•  ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋“ฑ๋ก
      ],
    }).compile();

    studyRoomRepository = module.get<StudyRoomRepository>(StudyRoomRepository);
	});
  • ์žฅ์ 
    • TypeORM์˜ ์‹ค์ œ ๋™์ž‘์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์—์„œ ์ž‘๋™ํ•ด์„œ ๋น ๋ฅด๊ณ , ํ…Œ์ŠคํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ฐ„์„ญ์ด ์—†๋‹ค.
    • TypeORM ์Šคํ‚ค๋งˆ ๋™๊ธฐํ™”(synchronize: true)๋กœ ํ…Œ์ด๋ธ” ์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ
  • ๋‹จ์ 
    • ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค.
  • ํ†ตํ•ฉ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์ ํ•ฉํ•˜๋‹ค.

3. ์›๋ณธ Repository ์ฃผ์ž…

beforeEach(async () => {
	const module: TestingModule = await Test.createTestingModule({
		providers: [DataRepository],
	}).compile();
	
	dataRepository = module.get(DataRepository);
});
  • ์žฅ์ 
    • ์‹ค์ œ DB์™€ ๊ฐ€์žฅ ๊ทผ์ ‘ํ•œ ํ…Œ์ŠคํŠธ
    • TypeORM์˜ ๋™์ž‘ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฒฐ๊ณผ๊นŒ์ง€ ํ™•์ธ ๊ฐ€๋Šฅ
    • ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ์˜ ๋™์ž‘์„ ์ •ํ™•ํžˆ ๊ฒ€์ฆ
  • ๋‹จ์ 
    • ์™ธ๋ถ€ DB์— ์˜์กดํ•˜๋ฏ€๋กœ ์™ธ๋ถ€ ์˜์กด์„ฑ์ด ๋†’๋‹ค.
    • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค.
    • ํ…Œ์ŠคํŠธ ์†๋„๊ฐ€ ๋А๋ฆฌ๋‹ค.
    • ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์™€ ๋ฐ์ดํ„ฐ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.
  • End-to-End ํ…Œ์ŠคํŠธ์— ์ ํ•ฉํ•˜๋‹ค.

4. Testconainer ํ™œ์šฉ

beforeAll(async () => {
  const container = await new GenericContainer("mysql")
    .withExposedPorts(3306)
    .withEnv("MYSQL_ROOT_PASSWORD", "root")
    .withEnv("MYSQL_DATABASE", "test_db")
    .start();

  const port = container.getMappedPort(3306);
  const host = container.getHost();

  const module: TestingModule = await Test.createTestingModule({
    imports: [
      TypeOrmModule.forRoot({
        type: 'mysql',
        host,
        port,
        username: 'root',
        password: 'root',
        database: 'test_db',
        entities: [StudyRoom],
        synchronize: true,
      }),
      TypeOrmModule.forFeature([StudyRoomRepository]),
    ],
  }).compile();

  studyRoomRepository = module.get<StudyRoomRepository>(StudyRoomRepository);
});
  • ์žฅ์ 
    • ์‹ค์ œ DB ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.
    • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด ๋‹ฌ๋ผ์ ธ๋„, ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
    • ํ…Œ์ŠคํŠธ ํ›„ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ข…๋ฃŒ๋˜๋ฏ€๋กœ ๊น”๋”ํ•œ ์ •๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ๋‹จ์ 
    • ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋„์šฐ๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์‹œ๊ฐ„์ด ์ถ”๊ฐ€๋กœ ์†Œ์š”๋œ๋‹ค.
    • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค.
  • ๋ณด๋‹ค ์ •๊ตํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์— ์ ํ•ฉํ•˜๋‹ค.

์ฐธ๊ณ  ๋ฌธ์„œ

https://stackoverflow.com/questions/55366037/inject-typeorm-repository-into-nestjs-service-for-mock-data-testing

https://docs.nestjs.com/techniques/database

https://docs.nestjs.com/fundamentals/custom-providers

https://velog.io/@lsjbh45/Nest.jsTypeORM-Repository%EC%9D%98-%EC%9D%BC%EB%B6%80%EB%A7%8C%EC%9D%84-mocking%ED%95%B4-Custom-Repository%EC%9D%98-method%EB%93%A4-test%ED%95%98%EA%B8%B0


๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š” ์ด์œ 

  • Repository

    • DB์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด
    • In-memory DB๋‚˜ ๋‹ค๋ฅธ DB ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•˜์—ฌ ํ…Œ์ŠคํŠธ

    โ‡’ ๋ฐฐํฌ ํ™˜๊ฒฝ์„ ๋ชจ๋ฐฉํ•˜์—ฌ ์•ˆ์ •์„ฑ๊ณผ ํ˜ธํ™˜์„ฑ์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ DB๋ฅผ ์‚ฌ์šฉ

  • Service

    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์˜๋„ํ•œ ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด
    • Custom Repository๋ฅผ ์˜์กด์„ฑ ์ฃผ์ž…ํ•˜์—ฌ ํ…Œ์ŠคํŠธ

    โ‡’ Repository layer๋ฅผ Mocking

  • Controller

    • API ์š”์ฒญ ๋ฐ ์‘๋‹ต์ด ์˜ˆ์ƒ๋Œ€๋กœ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด
    • Custom Service๋ฅผ ์˜์กด์„ฑ ์ฃผ์ž…ํ•˜์—ฌ ํ…Œ์ŠคํŠธ

    โ‡’ Service layer๋ฅผ Mocking

๐Ÿš€ ํ”„๋กœ์ ํŠธ ๊ทœ์น™

๐Ÿš€ ํ”„๋กœ์ ํŠธ ๊ธฐํš

๐Ÿ“ฝ ๋ฐ์ผ๋ฆฌ ์Šคํฌ๋Ÿผ

๐Ÿ‘ฅ ๊ทธ๋ฃน ํšŒ๊ณ 

๐Ÿ““ ๋ฉ˜ํ† ๋ง ์ผ์ง€

๐Ÿ“† ํšŒ์˜๋ก

๐Ÿง ๊ฐœ๋ฐœ์ผ์ง€

Clone this wiki locally