티스토리 뷰

Backend/꾸준히 TIL

[Jest+NestJS] Jest 테스트 환경세팅

개발하는 후딘 2022. 11. 5. 18:31
728x90
반응형

[ Cannot find module 해결하기 ]

(에러문)

Cannot find module 'src/entities/enums/PostType' from 'posts/posts.service.ts'

 

(해결책)

package.json 에서 "roots", "modulePaths", "moduleDirectories", "moduleNameMapper" 추가

moduleNameMapper 은 jest가 파일을 찾는 방식을 나타낸다.

jest는 절대경로를 파악하지 못하고, 상대경로로 파일의 위치를 파악한다.

{	
 ... ,
 "jest": {
 	"moduleFileExtensions": ["js","json","ts"],
 	"rootDir": "src",
 	"roots": [ "<rootDir>" ],
 	"modulePaths": [ "<rootDir>" ],
 	"moduleDirectories": [ "node_modules" ],
	"moduleNameMapper": { "^src/(.*)$": "<rootDir>/$1" },
    ...,
  }
}

 

[참고]

 

Jest | Cannot find module 'src/' from 'src/' 해결방법

테스트코드를 짜봅시 앗 고장났네

velog.io

 

Jest gives `Cannot find module` when importing components with absolute paths

Receiving the following error when running Jest Cannot find module 'src/views/app' from 'index.jsx' at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:179:17) at Object.<

stackoverflow.com


[ DataSource 를 찾지 못하는 에러 ]

Nest can't resolve dependencies... Please make sure that the argument DataSource at index[0] is available in the RootTestModule context. 

Nest can't resolve dependencies of the PostsService (?, PostsRepository). Please make sure that the argument DataSource at index [0] is available in the RootTestModule context.

 

(에러해석)

PostService는 repository가 필요한데, 테스트 모듈에서는 repository를 제공하지 않아서 생기는 문제이다.

 

(원인) 

// posts.service.spec.ts

describe('PostsService', () => {
  let service: PostsService;

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

    service = module.get<PostsService>(PostsService);
  });
  
  ...
}

 

(시도1) 

posts.module.ts 에 내가 주입한 엔티티를 확인할 수 있다.

/// posts.module.ts

import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Posts } from '../entities/Posts';

@Module({
  imports: [TypeOrmModule.forFeature([Posts])], // 내가 주입한 엔티티들
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

 

createTestingModule

imports: [ TypeOrmModule.forRoot(), TypeOrmModule.forFeature([ 엔티티 ] )] 를 추가한다.

// posts.servicee.spec.ts

describe('PostsService', () => {
  let service: PostsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([Posts])], // 추가
      providers: [PostsService],
    }).compile();

    service = module.get<PostsService>(PostsService);
  });
}

이번엔... 시간초과가 떴다... 데이터베이스에 접근하지 못했다는 것이다.

다른 방법을 찾아봐야겠다 ㅠㅠ

 

[참고]

더보기

 


[ Joi 설치하여, Mock 데이터베이스 만들기 ]

- 패키지 설치

$ npm i @nestjs/config @nestjs/mapped-types @nestjs/typeorm typeorm mysql2 joi

$ npm i class-validator class-transformer

 

- app.module.ts

import * as Joi from 'joi';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: '.env',
      isGlobal: true,
      validationSchema: Joi.object({
        NODE_PORT: Joi.string().required(),
        MYSQL_HOST: Joi.string().required(),
        MYSQL_PORT: Joi.string().required(),
        MYSQL_USERNAME: Joi.string().required(),
        MYSQL_PASSWORD: Joi.string().required(),
        MYSQL_DATABASE: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRootAsync({
      imports: [MySqlConfigModule],
      useClass: MySqlConfigService,
    }),
    AuthModule,
    PostsModule,
  ],
  controllers: [AppController],
  providers: [
    { provide: APP_FILTER, useClass: HttpExceptionFilter },
    AppService,
  ],
})

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): any {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

 

- posts.module.ts

import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Posts } from '../entities/Posts';

@Module({
  imports: [TypeOrmModule.forFeature([Posts])],
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

 

 

- 테스트 를 위한 패키지 설치

$ npm i -D @nestjs/testing

 

"npm run test" 로 테스트 케이스를 실행시켜봤지만 또에러가 나왔다 ...ㅎ

 

[에러]

Nest could not find PostsService element (this provider does not exist in the current context)

26 | }).compile();
27 |
> 28 | service = module.get<PostsService>(PostsService);
29 | postRepository = module.get<MockRepository<Posts>>(
30 | getRepositoryToken(Posts),
31 | );

at InstanceLinksHost.get (../node_modules/@nestjs/core/injector/instance-links-host.js:15:19)
at TestingModule.find (../node_modules/@nestjs/core/nest-application-context.js:221:55)
at TestingModule.get (../node_modules/@nestjs/core/nest-application-context.js:54:20)
at Object.<anonymous> (posts/test/posts.service.spec.ts:28:22)

 

Jest의 테스트케이스를 불러올 때, 테스트 대상 서비스의 생성자의 필드를 확인해야한다.

(수정이전)

// posts.service.ts

@Injectable()
export class PostsService {
  constructor(
    private dataSource: DataSource,
    @InjectRepository(Posts)
    private readonly postsRepository: Repository<Posts>,
  ) {}
  
  ...

}

 

(수정후)

// posts.service.ts

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Posts)
    private readonly postsRepository: Repository<Posts>,
  ) {}
 
	...
    
}

 

// posts.service.spec.ts

const mockPostRepository = () => ({
  save: jest.fn(),
  find: jest.fn(),
  findOne: jest.fn(),
  softDelete: jest.fn(),
});

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

describe('PostsService', () => {
  let service: PostsService;
  let postRepository: MockRepository<Posts>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PostsService,
        {
          provide: getRepositoryToken(Posts),
          useValue: mockPostRepository(),
        },
      ],
    }).compile();

    service = module.get<PostsService>(PostsService);
    postRepository = module.get<MockRepository<Posts>>(
      getRepositoryToken(Posts),
    );
  });

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

 

내가 DataSource를 사용한 이유는 QueryRunner을 호출하여 transaction 처리를 위해서였다.

여러개의 sql문을 호출할 때의 경우에서

문제가 발생하면 rollback하고, 문제가 없으면 commit처리해서 트랜잭션 관리를 위해서 했다.

서비스 생성자에서 DataSource 를 지웠더니 jest 테스트가 정상동작하게 되었다.

조금 궁금한게 생겼다. QueryRunner 와 QueryBuilder 의 차이가 뭔지 궁금했다.

내가 기억하기로는, QueryRunner은 수동으로 트랜잭션을 관리하고, QueryBuilder은 자동으로 관리하는걸로 알고있다.

 

이번에는 테스트케이스를 추가했더니... createQueryBuilder를 모르는 에러가 떴다.

    TypeError: this.postsRepository.createQueryBuilder(...).insert(...).into is not a function

      76 |         .createQueryBuilder('posts')
      77 |         .insert()
    > 78 |         .into(Posts)
         |          ^
      79 |         .values({
      80 |           ...createPostDto,
      81 |           userId: user.userId,

 

(해결책) mockRepository 에서 createQueryBuilder 을 정의를 해줘야한다.

// posts.service.spec.ts

const mockPostRepository = jest.fn(() => ({
  create: jest.fn(),
  save: jest.fn(),
  find: jest.fn(),
  findOne: jest.fn(),
  softDelete: jest.fn(),
  createQueryBuilder: jest.fn().mockReturnValue({
    insert: jest.fn().mockReturnThis(),
    into: jest.fn().mockReturnThis(),
    values: jest.fn().mockReturnThis(),
    execute: jest.fn().mockReturnThis(),
  }),
}));

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

describe('PostsService', () => {
  let service: PostsService;
  let postRepository: MockRepository<Posts>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PostsService,
        {
          provide: getRepositoryToken(Posts),
          useValue: mockPostRepository(),
        },
      ],
    }).compile();

    service = module.get<PostsService>(PostsService);
    postRepository = module.get<MockRepository<Posts>>(
      getRepositoryToken(Posts),
    );
  });

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

  describe('Public-Post', () => {
    const newPublicPost: CreatePostDto = {
      title: '공개 게시글 테스트',
      content: '공개 게시글의 본문입니다 ~~~~😺',
      postType: PostType.PUBLIC_POST,
    };

    const user = new Users();
    user.userId = 1;
    user.name = '기스깅';

    it('공개 게시글 등록 성공', async () => {
      postRepository.save.mockResolvedValueOnce(newPublicPost); // 등록성공 가정
      const result = await service.createPost(user, newPublicPost);

      expect(postRepository.save).toBeCalled(); // save 가 불러졌음.
      expect(postRepository.save).toHaveBeenCalledWith(newPublicPost);
      expect(result).toEqual(newPublicPost); // 이 createPosts() method결과가 newPublicPosts가 와 같니?
    });
    it.todo('공개 게시글 등록 실패 - 제목길이 20자 초과');
    it.todo('공개 게시글 등록 실패 - 본문길이 200자 초과');
  });
});

 

 

 

 

NestJS Testing (Jest)

NestJS와 Database와 연결하여 게시글 CRUD 작업에 대한 Unit Test와 End-to-end Test를 진행해봅니다.

velog.io

 

[JEST] 📚 유용한 Matcher 함수 종류 모음

JEST Matcher 정리 Jest는 다른 방법으로 값을 테스트 하도록 matcher 라는 것을 사용한다. matcher란 '이거 맞아?' 라고 물어보는 메서드리고 보면 된다. 기대한 값이 실제 반환된 값과 일치하는 지를 확

inpa.tistory.com

 

NestJS 11 | Unit Test - QueryBuilder & Transaction

이번엔 QueryBuilder와 Transaction을 활용하는 경우 Unit Test를 어떻게 작성하는지 살펴보도록 하겠습니다.

velog.io

 

[참고]

 

더보기

 


[ Cannot find module 'passport' 에러 ]

(해결책)

$ npm i passport
$ npm i -D @types/passport

 

 

Cannot find module 'passport' 해결 방법

그냥 같이 깔리면 안돼?

velog.io

 


[ validationSchema: Joi.object({ ... }) ]

(원인) 

import Joi from 'joi';

 

(해결책)

import * as Joi from 'joi';

 

 

Joi getting validationSchema: Joi.object() error

After installing joi on nestjs framework i'm trying to validate two property by joi something like: import Joi from 'joi'; ... ConfigModule.forRoot({ isGlobal: true, validationSchema: Joi.o...

stackoverflow.com


 

 

단위 테스트 케이스 예제

 

NestJS에서 단위 테스트 작성하기

이 글에서 사용하는 단위 테스트 코드는 여기에서 확인할 수 있습니다. 하지만 저장소를 Mock/Stub 처리해서 작성한 단위 테스트입니다. 저장소를 Mock/Stub 처리하지 않고 작성한 테스트 코드는 여

jhyeok.com

 

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함