Node, Nest, Deno/🦁 Nest - Series

Nest + Jest unit test (5) spy function, mockImplementation

DarrenKwonDev 2020. 12. 12. 20:58

jest.spyOn(object, methodName) 

 

Jest · 🃏 Delightful JavaScript Testing

🃏 Delightful JavaScript Testing

jestjs.io

* spy 함수는 언제 사용해야 하나

 

toHaveBeenCalledTimes, toHaveBeenCalledWith 등을 사용할 때 expect 되는 메서드는 mock 된 것이거나 spy function이어야 합니다. 아무런 가공을 거치지 않은 일반 함수를 쓰게 되면 Matcher error: received value must be a mock or spy function 가 발생합니다.

 

이전까지는 mock을 사용해보았으나 spy function이 무엇인지는 살펴보지 않았습니다. spy function은 주로 mock을 할 수 없을 때 사용합니다.

 

언제 사용할 수가 없을까요? 테스팅해야 하는 함수가 테스트할 다른 함수 내에서도 사용되어 mocking을 할 경우 사용되는 함수를 테스트할 수 없기 때문에 원본은 두고 spy 해야 할 때가 있습니다.

 

쉽게 예시를 들어 보겠습니다.

deletePodcast 함수는 getPodcasat를 내부에 사용합니다. 이 경우 getPodcast를 mocking할 경우 getPodcast를 테스팅할수 없게 됩니다. 이런 경우에 spy를 사용합니다.

async getPodcast(id: number): Promise<PodcastOutput> {
  try {
    const podcast = await this.podcastRepository.findOne(
      { id },
      { relations: ['episodes'] },
    );
    if (!podcast) {
      return {
        ok: false,
        error: `Podcast with id ${id} not found`,
      };
    }
    return {
      ok: true,
      podcast,
    };
  } catch (e) {
    console.log(e);
    return this.InternalServerErrorOutput;
  }
}


 async deletePodcast(id: number): Promise<CoreOutput> {
  try {
  
    // deletePodcast는 getPodcast함수를 사용합니다. 따라서 getPodcast는 mocking해야 합니다.
    const { ok, error } = await this.getPodcast(id);
    if (!ok) {
      return { ok, error };
    }
    await this.podcastRepository.delete({ id });
    return { ok };
  } catch (e) {
    console.log(e);
    return this.InternalServerErrorOutput;
  }
}

 

 

jest
  .spyOn(service, 'getPodcast')
  .mockImplementation(async id => InternalServerErrorOutput);

 

 

* 그래서 spy 함수는 어떻게 사용하나

 

jest.spyOn으로 spy는 아래와 같이 진행할 수 있으며

mockImplementation을 통해서 해당 함수를 실행한다면 해당 스코프 내에서 mock을 한 것과 같은 효과를 냅니다. 실제 함수가 실행되는 것이 아니라 mockImplementation 내에 정의한 함수가 사용됩니다.

 

예시 1.

describe('deletePodcast', () => {
  const ID = 1;
  it('fail on getPodcast failed', async () => {
    jest
      .spyOn(service, 'getPodcast')
      .mockImplementationOnce(async id => InternalServerErrorOutput);
      
    const result = await service.deletePodcast(ID);
    
    expect(result).toEqual(InternalServerErrorOutput);
  });
});

 

예시 2.

describe('MailService', () => {

  describe('sendVerificationEmail', () => {
    it('should call sendEmail', () => {
      const sendVerificationEmailArgs = {
        to: 'email',
        email: 'email',
        code: 'code',
      };
      
      // sendEmail 함수를 spy합니다. mocking한다면 sendEmail 테스팅을 할 수 없을 것입니다
      jest.spyOn(service, 'sendEmail').mockImplementation(async () => {});

      service.sendVerificationEmail(
        sendVerificationEmailArgs.to,
        sendVerificationEmailArgs.email,
        sendVerificationEmailArgs.code,
      );

      expect(service.sendEmail).toHaveBeenCalledTimes(1);
      expect(service.sendEmail).toHaveBeenCalledWith('Verify Your Email', sendVerificationEmailArgs.to, 'ubereats', [
        { key: 'username', value: sendVerificationEmailArgs.email },
        { key: 'code', value: sendVerificationEmailArgs.code },
      ]);
    });
  });
});