0. Overview

Examples

  jest.spyOn(서비스클래스, 'Mocking 할 메소드명')
    .mockImplementation(() => {
    	// 메소드 내용 
  	});
  jest.spyOn(CouponService, 'checkUser')
    .mockImplementation(async (id: string): Promise<User> => {
    	return new User();
  	});

Spec

project:
  framework: 
    - nestjs
    - jest
  database:
    - dynamodb
  versions:
    nodejs: 14.2
    typescript: 3.7
    nest: 7.0
    jest: 25.1

1. Preface

이번에 참가한 프로젝트에서는 "아직 개발되지 않은 기능"을 이용해서 개발해야하는 경우가 상당히 많았습니다. 예를 들면, "회원의 쿠폰 데이터를 불러오고 싶은데 회원 정보를 불러오는 기능이 아직 개발되지 않은 상황"이라는 겁니다. 이러한 경우가 있을 때마다 개발되지 않은 부분의 함수를 mocking 하여 테스트 코드를 작성했었는데, 이럴 때 사용하는 것이 jest 의 spyOn().mockImplementation() 함수입니다.

이번 포스트에서는 자주 사용하고 있는 jest.spyOn().mockImplementation() 메소드에 대해 간략히 정리하고자 합니다.

2. Problem

code1 은 회원의 ID 를 파라미터로 받아 해당 회원의 쿠폰 리스트를 불러오는 코드입니다.

  1) 해당 유저의 ID 로 유저가 실제 존재하는 유저인지 확인한다.
  2) 해당 유저가 실제 존재하는 유저라면 DB 에서 유저가 가지고 있는 쿠폰 리스트를 가져온다.

여기서 1) 해당 유저가 실제 존재하는 유저인지 확인한다. 부분이 아직 개발되지 않은 기능라고 가정하겠습니다.
UserService.fetchUser() 가 아직 개발되지 않았기에 fetchUserCoupons() 의 테스트를 할 수 없는 상황입니다.

code1) coupon.service.ts

export class CouponService {
  constructor(
  	private readonly db: DbService, 
    private readonly user: UserService
  ) {}
    
  public async fetchUserCoupons(id: string): Promise<CouponDto[]> {
    // Mocking 할 부분
	await this.checkUser(id);
   	
    // fetch coupon data
    const params = {
      TableName: 'CouponTable',
      IndexName: 'user',
      ProjectionExpression: 'id, counponName, #type',
      KeyConditionExpression: 'userId = :id AND #status = :status',
      ExpressionAttributeNames: {
        '#status': 'status',
        '#type': 'type',
      },
      ExpressionAttributeValues: {
        ':id': id,
        ':status': 'ACTIVE',
      },s
      Limit: 10,
    }
    const result: any[] = await this.db.query(params);
    return result.map(item => {
           	 return {
               id: item.id,
               counponName: item.couponName,
               type: item.type,
             }
           });
  }
  
  public async checkUser(id: string): Promise<User> {
    const user: User = await this.user.fetchUser(id);    // 아직 개발되지 않은 기능
    if (!user) {
      console.error('f-user-err', id);
      throw new Error('not exist user');
    }
    return user;
  }
}

3. How to solve

이 상황에서는 UserService.fetchUser() 가 아직 개발되지 않은 상황이기에 CouponService 의 checkUser() 를 mocking 한다면 테스트를 진행할 수 있습니다.

왜 UserService.fetchUser() 메소드가 아닌 checkUser() 를 mocking 하는가하면, 테스트하고자 하는 메소드가 fetchUserCoupons() 이기 때문입니다. fetchUserCoupons() 에서 불러오는 메소드는 checkUser() 이지 UserService.fetchUser() 가 아닙니다. 따라서 현재 테스트하고자하는 메소드에서 직접적으로 호출하는 메소드인 checkUser() 를 mocking 하여 테스트하는 것이 맞다고 생각했습니다.

checkUser() 를 Mocking 해보겠습니다. jest 의 spyOn().mockImplementation() 기능을 사용하면 checkUser() 를 새롭게 테스트용으로 만들어 실행할 수 있습니다. code2 는 전체 테스트 코드입니다.

jest.spyOn().mockImplementation()

  • jest.spyOn() 의 파라미터에 Mocking 할 함수가 있는 클래스와 함수명을 넣어줍니다.
    • jest.spyOn(Mocking 할 함수가 있는 클래스, 'Mocking 할 함수명')
    • ex)
        jest.spyOn(CouponService, 'checkUser');
  • mockImplementation() 에 새롭게 정의할 메소드 내용을 정의합니다. 함수 시그니처는 동일하게 작성합니다.((string) => User)
    • ex)
        mockImplementation(async (id: string): Promise<User> => {
          return {
            id: id,
          };
        });

code2) coupon.service.spec.ts

import {Test, TestingModule} from '@nestjs/testing';

describe('CouponSerivceSpec', () => {
  let service: CouponService;
  
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [CouponModule]
    }).compile();
    
    service = module.get<CouponService>(CouponService);
  });
  
  it('should be defined', () => {
    expect(service).toBeDefined();
  });
  
  it('fetchUserCoupons', async () => {
    jest.setTimeout(200_000_000);
    
    jest.spyOn(service, 'checkUser').mockImplementation(
      async (id: string): Promise<User> => {
        return {
          id: id,
          name: 'test-name',
          age: 22,
        }
      }
    );

    const id = 'test-user-id';
    const result = await service.fetchUserCoupons(id);

    expect(result).toBeDefined();
  }
})

4. Conclusion

jest.spyOn() 은 테스트하는 함수 내부에서 호출되는 함수가 제대로 호출되었는지, 어떻게 호출되었는지를 확인하는 용도로 사용되는 함수입니다.

여기에 mockImplementation() 를 이용하면 단순히 Mock 함수를 만들어 고정된 값을 반환하는 것이 아니라, 실제 호출하는 것과 동일하게 호출되는 Mock 함수를 만들수 있습니다. 함수 시그니처도 동일하게 작성하기에 테스트용 Mock 함수라도 더 실제 같은 Mock 함수를 만들어 테스트할 수 있습니다.


profile
Tokyo Dev

0개의 댓글