(최종 프로젝트 진행중)
※ 이하는 스스로 공부하며 적어둔 노트이며 불확실한 내용이 있을 수 있습니다. 학습용으로 적합하지 않음을 유념해주세요. ※
[Nest.js][Jest] 에러 TypeError: this.repository.create is not a function
[Nest.js][Jest] 에러 TypeError: this.repository.create is not a function
발생 상황: messages.service.spec.ts를 실행하는데 messages.service.ts 소스코드에 정의한 this.repository.create()이라는 TypeORM Repository의 기본 메소드가 인식되지 않는다고 함.
에러 메세지 전문:
FAIL src/messages/messages.service.spec.ts (7.845 s)
MessagesService
getMessageById
√ should be defined (14 ms)
× should create a new message and return that (6 ms)
● MessagesService › getMessageById › should create a new message and return that
TypeError: this.repository.create is not a function
44 |
45 | async create(createMessageDto: CreateMessageDto) {
> 46 | const newMessage = this.repository.create(createMessageDto);
| ^
47 |
48 | return this.repository.save(newMessage);
49 | }
at MessagesService.create (messages/messages.service.ts:46:40)
at Object.<anonymous> (messages/messages.service.spec.ts:94:28)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 8.454 s
Ran all test suites matching /messages.service/i.
시도:
시도1: 일단 @InjectRepository를 서비스 의 생성자에 붙이면 Repository<Message>의 모든 메소드를 이용할 수 있게 된다고 ㅎ나다. 이 때 짝으로 설정해줘야 하는 것은 TypeOrmModule.forFeature에 Entity를 넣어서 module에 import해줘야 한다는 것. 이런 것처럼:
@Injectable()
export class ProductService {
private logger = new Logger('ProductService');
constructor(
@InjectRepository(Product)
private productRepository: Repository<Product>
) { }
async getAllAsync(): Promise<Product[]> {
return await this.productRepository.find();
}
async getCountAsync(): Promise<number> {
return await this.productRepository.count();
}
}
⇒ 나는 이미 이렇게 하고 있었음.
시도2: service.spec.ts에 mock method를 제대로 안 넣어줘서 이 에러가 났다는 사람도 있었다. 그래서 인식되지 않는다는 해당 메소드를 제대로 mocking해주니 해결됐다고.
// 답변자의 예시:
const mockUserRepository = {
register: jest.fn().mockResolvedValue('sampleResolvedValue')
};
// 내 코드엔 원래 제대로 되어 있었음:
const mockMessagesRepository = {
create: jest.fn().mockImplementation((dto) => dto),
save: jest
.fn()
.mockImplementation((message) => Promise.resolve({ id: 1, ...message })),
};
⇒ 나는 이미 잘 mocking 해준 상태였음.
시도3: 헐 보다보니 beforeEach에서 mockTestingModule을 초기화할 때 정작 mockMEssagesRepository를 안 넣어준 상태였다..! ⇒ 해결됨
원인: 테스트 코드에서 mock repository를 제대로 주입해주지 않아서 생긴 에러였다. mockMessageRepository를 다 만들어 뒀어도 정작 testingModule을 초기화할 때 주입할 자리에 그냥 빈 객체 {}
를 지정해 둔 상태였던 것이다. 그러니 create이라는 메소드가 정의되지 않은 상태가 맞았지...
해결: createTestingModule()의 provider 파트에 repository를 커스텀 mockMessagesRepository로 제대로 지정해주니 해결됨.
// (원래 만들어 둔) mock repository:
const mockMessagesRepository = {
create: jest.fn().mockImplementation((dto) => dto),
save: jest
.fn()
.mockImplementation((message) => Promise.resolve({ id: 1, ...message })),
};
// 이렇던 것을:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MessagesService,
{
provide: getRepositoryToken(Message),
useValue: {},
},
],
}).compile();
})
// 이렇게 바꿔줌:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MessagesService,
{
provide: getRepositoryToken(Message),
useValue: mockMessagesRepository,
},
],
}).compile();
})
알게 된 점: 내가 제대로 된 mock repository를 제공하지 않아서 ‘그런 repository.문제메소드
는 찾을 수가 없다’고 에러가 뜨는 상황이라도, 에러가 가리키는 곳은 실제 service 소스 코드의 this.repository.문제메소드
였다. 앞으로는 단위 테스트 때 이와 비슷하게 ‘mock이 주입되어야 하는 provider의 실제 소스’를 가리키는 것 같은 에러가 나도 “이 provider는 mocking해주는 생황이니 문제는 테스트 코드에 있다” 하고 알아서 판단해 수정할 부분을 찾을 수 있을 것 같다.
[Nest.js][Jest] 모듈 별 e2e 테스트
[Nest.js][Jest] 모듈 별 e2e 테스트
- test/messages.e2e-spec.ts 파일 생성
- app.e2e-spec.ts 파일 복사 붙여넣기
- import 부분을 appModule 대신 messagesModule로 대체
- mockMessagesRepository를 (messages.service.spec.ts에서와 똑같이) override해준다.
- validationPipe app에 지정해주기.
- 이제 'GET /messages’처럼 테스트를 원하는 api 경로로 테스트하면 된다.
supertest Docs 참고
supertest Test.set
예시 모음집
e2e 내 각 it() 마다 request(app.getHttpServer()) 를 리턴해줘야 실제로 테스트가 된다.
return을 안 넣어도 테스트가 되길래 빼버렸더니, 뭘 넣어도 그냥 계속 통과되기만 한다는 것을 알게 되었다.
return 을 빼먹었을 때:
// test/requests.e2e-spec.ts
it('GET /requests', () => {
request(app.getHttpServer())
.get('/requests')
.expect(400) // 200이어야 함.
.expect(getRequestsSample);
});
=> 통과!?
return을 넣어줬을 때: ‘200이어야 하는데 400이 들어왔다’고 제대로 검증할 수 있음.
it('GET /requests', () => {
return request(app.getHttpServer())
.get('/requests')
.expect(400)
.expect(getRequestsSample);
});
=> expected 400 "Bad Request", got 200 "OK"
실제로 ‘잘못된 타입’을 body로 줬을 때 테스트가 실패하게 하고 싶다면
main.ts에 ValidationPipe를 써준 것처럼
// src/main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
테스트하려는 e2e-spec.ts 내의 mock 모듈에도 똑같이 지정해줘야 들어오는 인자값들의 타입을 실제로 검증할 수 있게 된다:
// test.requests.e2e-spec.ts
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [RequestsModule],
})
.overrideProvider(getRepositoryToken(Request))
.useValue(mockRequestsRepository)
.compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe()); // <- new!
await app.init();
});
app.useGlobalPipes(new ValidationPipe());
을 지정해주지 않았을 때:
//
it('fails when "detail" is not a string', () => {
return request(app.getHttpServer())
.post('/requests')
.send({
reserved_time: new Date('2023-05-05'),
detail: 123123,
})
.expect('Content-Type', /json/)
.expect(201); // 400이어야 함.
});
=> 통과!?
app.useGlobalPipes(new ValidationPipe());
을 지정해준 후: ‘201을 예상했지만 400이 발생했다’고 제대로 검증할 수 있게 됨:
=>
expected 201 "Created", got 400 "Bad Request"
(참고 - 거의 이것 보고 작성하고 에러 잡아낸 유튜브: )
[Nest.js] 에러 Nest can't resolve dependencies of the RequestRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.
[Nest.js] 에러 Nest can't resolve dependencies of the RequestRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.
발생 상황: e2e 테스트 처음 실행시 발생.
// test/requests.e2e-spec.ts
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { RequestsService } from '../src/requests/requests.service';
import { RequestsModule } from '../src/requests/requests.module';
describe('Requests (e2e)', () => {
let app: INestApplication;
let requestsService = {
getRequests: () => getRequestsResult,
getRequestById: (id) => getRequestByIdResult, // ? params로 받는 값을 어떻게 넣어주지.
createRequest: () => createRequestResult,
updateRequestById: () => updateRequestByIdResult,
deleteRequestById: () => deleteRequestByIdResult,
};
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [RequestsModule],
})
.overrideProvider(RequestsService)
.useValue(requestsService)
.compile();
app = module.createNestApplication();
await app.init();
});
it('/GET requests', () => {
return request(app.getHttpServer())
.get('/requests')
.expect(200)
.expect({ data: requestsService.getRequests() });
});
afterAll(async () => {
await app.close();
});
});
에러 메세지 전문:
Watch Usage: Press w to show more.
FAIL test/requests.e2e-spec.ts (5.709 s)
● Requests (e2e) › /GET requests
Nest can't resolve dependencies of the RequestRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.
Potential solutions:
- Is TypeOrmModule a valid NestJS module?
- If DataSource is a provider, is it part of the current TypeOrmModule?
- If DataSource is exported from a separate @Module, is that module imported within TypeOrmModule?
@Module({
imports: [ /* the Module containing DataSource */ ]
})
47 |
48 | beforeAll(async () => {
> 49 | const module = await Test.createTestingModule({
| ^
50 | imports: [RequestsModule],
51 | })
52 | .overrideProvider(RequestsService)
at TestingInjector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:247:19)
at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:200:33)
at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../node_modules/@nestjs/core/injector/injector.js:120:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:135:27)
at TestingInjector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:61:13)
at TestingInjector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:88:9)
at ../node_modules/@nestjs/core/injector/instance-loader.js:49:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstancesOfProviders (../node_modules/@nestjs/core/injector/instance-loader.js:48:9)
at ../node_modules/@nestjs/core/injector/instance-loader.js:33:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstances (../node_modules/@nestjs/core/injector/instance-loader.js:32:9)
at TestingInstanceLoader.createInstancesOfDependencies (../node_modules/@nestjs/core/injector/instance-loader.js:21:9)
at TestingInstanceLoader.createInstancesOfDependencies (../node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../node_modules/@nestjs/testing/testing-module.builder.js:97:9)
at TestingModuleBuilder.compile (../node_modules/@nestjs/testing/testing-module.builder.js:63:9)
at Object.<anonymous> (requests.e2e-spec.ts:49:20)
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'close')
94 |
95 | afterAll(async () => {
> 96 | await app.close();
| ^
97 | });
98 | });
99 |
at Object.<anonymous> (requests.e2e-spec.ts:96:15)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 5.762 s
Ran all test suites matching /requests.e2e/i.
시도:
원인:
- requests.module.ts에서 TypeOrmModule을 주입해주는데 왜 이런 오류가 발생하는 걸까
// src/requests/requests.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([Request]),
JwtModule.registerAsync({
imports: [ConfigModule],
useClass: JwtConfigService,
inject: [ConfigService],
}),
],
controllers: [RequestsController],
providers: [RequestsService],
})
export class RequestsModule {}
해결: beforeEach 파트를 다음과 같이 수정 시 해결됨
const mockRequestsRepository = {};
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [RequestsModule],
})
.overrideProvider(getRepositoryToken(Request))
.useValue(mockRequestsRepository)
.compile();
app = module.createNestApplication();
await app.init();
});
커스텀 repository를 만들었을 때
이렇게 하는 게 맞을까
// messages.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([Message]),
],
controllers: [MessagesController],
providers: [MessagesService, MessagesRepository],
})
export class MessagesModule {}
이렇게 하는 게 맞을까
// messages.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([MessagesRepository]),
],
controllers: [MessagesController],
providers: [MessagesService, ],
})
export class MessagesModule {}
일단 service.ts 에는 @InjectRepository를 지우고 이렇게 써주는 게 맞다:
// 기본 Repository<Message>가 아니라 커스텀 MessagesRepository를 넣어줄 때:
@Injectable()
export class RequestsService {
constructor(
@InjectRepository(Message) private repository: Repository<Message>
private readonly repository: MessagesRepository
) {}
...
}
node-mocks-http 모듈 도입
node-mocks-http: Express 애플리케이션 라우팅 함수를 테스트하기 위한 Mock 객체를 제공해주는 모듈.
create 계열 메소드를 테스트 할 때 인수 req.user를 구현하려고 도입함.
그리고 나중에 Authorization header도 사용할 수 있을까 해서…
mockRequest = httpMocks.createRequest();
mockResponse = httpMocks.createResponse();
const body = { name: 'John', age: 39 }
mockRequest.body = body;
(참고: https://libertegrace.tistory.com/entry/TDD-node-mocks-http-모듈)
(https://www.youtube.com/watch?v=43iQzPJvZDw)
⇒ 그러나 테스트 때 과한 3rd-party 모듈을 도입하는 것을 지양해야 한다고 하기에 도입하지 않기로 함.
Date의 toString() 종류 총정리:
reserved_time = new Date('2023-05-05’)
⇒이것을 "2023-05-05", 처럼 출력하고 싶다.
- "디폴트(reserved_time 그대로)": 2023-05-05T00:00:00.000Z,
- “toLocaleString": "2023. 5. 5. 오전 9:00:00",
- "toDateString": "Fri May 05 2023",
- "toISOString": "2023-05-05T00:00:00.000Z",
- "toJSON": "2023-05-05T00:00:00.000Z",
- "toLocaleDateString": "2023. 5. 5.",
- "toLocaleTimeString": "오전 9:00:00",
- "toString": "Fri May 05 2023 09:00:00 GMT+0900 (대한민국 표준시)",
- "toTimeString": "09:00:00 GMT+0900 (대한민국 표준시)",
일단 디폴트는 toJSON인 것 같다.
reserved_time.toJSON 혹은 reserved_time에서 .split(’T’)[0]를 고르면 되겠다. 아 잠깐만 그러면 다시 타입이 Date이 아니게 되는데…
Uploaded by N2T