From 1e7e2d8db15fb9814167bdeeb8d80947dcc648ec Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 5 Mar 2024 04:29:00 +0000 Subject: [PATCH 1/3] =?UTF-8?q?doc:=20Nest=E3=83=A2=E3=82=B8=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE=E4=BE=8B?= =?UTF-8?q?=E3=82=92CONTRIBUTING.md=E3=81=AB=E6=9B=B8=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3263bf6aa13..6a20700ec2e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -316,6 +316,148 @@ export const handlers = [ Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files. +## Nest + +### Unit Test +Short example + +```typescript +import { jest } from '@jest/globals'; // for spyOn +import { Test } from '@nestjs/testing'; +import type { TestingModule } from '@nestjs/testing'; +import { ModuleMocker } from 'jest-mock'; +import type { MockFunctionMetadata } from 'jest-mock'; +import { FooService } from '@/core/FooService'; +import { BarService } from '@/core/BarService'; +import { HogeService } from '@/core/HogeService'; +import { GlobalModule } from '@/GlobalModule.js'; + +const moduleMocker = new ModuleMocker(global); + +describe('test', () => { + let app: TestingModule; + let fooService: FooService; // for test case + let barService: BarService; // for test case + let hogeService: HogeService; // Let's mock this service as it is not very relevant + + beforeEach(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + FooService, + BarService, + ], + }) + .useMocker((token) => { + if (token === HogeService) { + // return mock + return { fugaMethod: jest.fn() }; + } + if (typeof token === 'function') { + const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; + const Mock = moduleMocker.generateFromMetadata(mockMetadata); + return new Mock(); + } + }) + .compile(); + + fooService = app.get(FooService); + barService = app.get(BarService); + hogeService = app.get(HogeService) as jest.Mocked; + }); + test('niceMethod', () => { + // spy + const bazSpy = jest.spyOn(barService, 'bazMethod'); + // mock return value + hogeService.fugaMethod.mockResolvedValue('hello, world' as any); + + // target function + await fooService.niceMethod(); + + // check values + expect(bazSpy).toHaveBeenCalled(); + }); +}) +``` + +### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合 + +どちらかのサービスで`OnModuleInit`を使う + +```typescript +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { BarService } from '@/core/BarService'; + +@Injectable() +export class FooService implements OnModuleInit { + private barService: BarService // constructorから移動してくる + + constructor( + private moduleRef: ModuleRef, + ) { + } + + async onModuleInit() { + this.barService = this.moduleRef.get(BarService.name); + } + + public async niceMethod() { + return await this.barService.incredibleMethod({ hoge: 'fuga' }); + } +} +``` + +#### Service Unit Test +テストで`@nestjs/testing`のTest.createTestingModuleを使う場合 + +```typescript +// import ... + +describe('test', () => { + let app: TestingModule; + let fooService: FooService; // for test case + let barService: BarService; // for test case + + beforeEach(async () => { + app = await Test.createTestingModule({ + imports: ..., + providers: [ + FooService, + { // mockする (mockが必須かどうか不明) + provide: BarService, + useFactory: () => ({ + incredibleMethod: jest.fn(), + }), + }, + { // Provideにする + provide: BarService.name, + useExisting: BarService, + }, + ], + }) + .useMocker(... + .compile(); + + fooService = app.get(FooService); + barService = app.get(BarService) as jest.Mocked; + + // onModuleInitを実行する + await fooService.onModuleInit(); + }); + + test('nice', () => { + await fooService.niceMethod(); + + expect(barService.incredibleMethod).toHaveBeenCalled(); + expect(barService.incredibleMethod.mock.lastCall![0]) + .toEqual({ hoge: 'fuga' }); + }); +}) +``` + ## Notes ### Misskeyのドメイン固有の概念は`Mi`をprefixする From c949b580487c0616f2ddf05e9425000236dc17c7 Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 5 Mar 2024 04:36:55 +0000 Subject: [PATCH 2/3] rm normal test --- CONTRIBUTING.md | 66 +------------------------------------------------ 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a20700ec2e2..6fa000029a20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -318,70 +318,6 @@ Don't forget to re-run the `.storybook/generate.js` script after adding, editing ## Nest -### Unit Test -Short example - -```typescript -import { jest } from '@jest/globals'; // for spyOn -import { Test } from '@nestjs/testing'; -import type { TestingModule } from '@nestjs/testing'; -import { ModuleMocker } from 'jest-mock'; -import type { MockFunctionMetadata } from 'jest-mock'; -import { FooService } from '@/core/FooService'; -import { BarService } from '@/core/BarService'; -import { HogeService } from '@/core/HogeService'; -import { GlobalModule } from '@/GlobalModule.js'; - -const moduleMocker = new ModuleMocker(global); - -describe('test', () => { - let app: TestingModule; - let fooService: FooService; // for test case - let barService: BarService; // for test case - let hogeService: HogeService; // Let's mock this service as it is not very relevant - - beforeEach(async () => { - app = await Test.createTestingModule({ - imports: [ - GlobalModule, - ], - providers: [ - FooService, - BarService, - ], - }) - .useMocker((token) => { - if (token === HogeService) { - // return mock - return { fugaMethod: jest.fn() }; - } - if (typeof token === 'function') { - const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata; - const Mock = moduleMocker.generateFromMetadata(mockMetadata); - return new Mock(); - } - }) - .compile(); - - fooService = app.get(FooService); - barService = app.get(BarService); - hogeService = app.get(HogeService) as jest.Mocked; - }); - test('niceMethod', () => { - // spy - const bazSpy = jest.spyOn(barService, 'bazMethod'); - // mock return value - hogeService.fugaMethod.mockResolvedValue('hello, world' as any); - - // target function - await fooService.niceMethod(); - - // check values - expect(bazSpy).toHaveBeenCalled(); - }); -}) -``` - ### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合 どちらかのサービスで`OnModuleInit`を使う @@ -426,7 +362,7 @@ describe('test', () => { imports: ..., providers: [ FooService, - { // mockする (mockが必須かどうか不明) + { // mockする (mockは必須ではないかもしれない) provide: BarService, useFactory: () => ({ incredibleMethod: jest.fn(), From 57b81c834469b65dc68ded35744a3a852cb3c540 Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 5 Mar 2024 05:05:33 +0000 Subject: [PATCH 3/3] forwardRef --- CONTRIBUTING.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fa000029a20..dcb625626d6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -320,7 +320,21 @@ Don't forget to re-run the `.storybook/generate.js` script after adding, editing ### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合 -どちらかのサービスで`OnModuleInit`を使う +#### forwardRef +まずは簡単に`forwardRef`を試してみる + +```typescript +export class FooService { + constructor( + @Inject(forwardRef(() => BarService)) + private barService: BarService + ) { + } +} +``` + +#### OnModuleInit +できなければ`OnModuleInit`を使う ```typescript import { Injectable, OnModuleInit } from '@nestjs/common'; @@ -346,8 +360,8 @@ export class FooService implements OnModuleInit { } ``` -#### Service Unit Test -テストで`@nestjs/testing`のTest.createTestingModuleを使う場合 +##### Service Unit Test +テストで`onModuleInit`を呼び出す必要がある ```typescript // import ...