Angular에서 주기적 polling 같은 걸 rxjs, ngrx 같은 걸로 구현해 놓으면 테스트에 실패가 발생한다. 마지막에 다음과 같은 에러가 발생하면서 문제가 발생한다. 주기적 Timer 들이 MicroTask로 계속 남아 있으면서 에러가 발생하는 걸로 보인다. 

    8 periodic timer(s) still in the queue.

      at node_modules/zone.js/dist/fake-async-test.js:617:31
      at ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:386:30)
      at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/proxy.js:117:43)
      at ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:385:36)
      at Zone.run (node_modules/zone.js/dist/zone.js:143:47)

이걸 해결하려면 test의 마지막에 discardPeriodicTasks()를 호출해주면, 정리 작업이 완료된다. 

 

또 한 가지 에러는 테스트 안의 expect의 assertion이 fail하면, 코드에 아무 문제가 없는 데도 아래와 같은 에러가 발생한다. 테스트가 정상적으로 완료되면 아래 에러는 없어진다. 

    Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.

 


WRITTEN BY
HanDDol
여행이란 건 말이지. 첫 걸음을 내딜 때는 모든 게 낯설고.. 그리고 점점 더 낯선 세상에 익숙해지면서 세상의 모든 곳이 고향처럼 느껴진다. 고향으로 돌아오는 여행의 마지막 걸음에는 나의 고향이 더 이상 익숙한 곳이 아닌 낯선 곳임을 알게 된다.

,

Jest에 Angular FlexLayout이 적용된 컴포넌트를 테스트하니 아래와 같은 에러가 발생한다. 

    TypeError: getComputedStyle(...).getPropertyValue is not a function

      at StyleUtils.lookupStyle (node_modules/@angular/src/lib/core/style-utils/style-utils.ts:94:47)
      at StyleUtils.hasWrap (node_modules/@angular/src/lib/core/style-utils/style-utils.ts:65:17)
      at DefaultFlexDirective.BaseDirective2.hasWrap (node_modules/@angular/src/lib/core/base/base2.ts:137:24)

 

한참을 찾아봐도 에러 원인을 알 수가 없다. 당연히 나지 말아야 할 에러니까.

 

찾다 보니 Jest는 DOM 관련된 모듈로 JSDOM을 사용한다. 문제는 JSDOM이 getComputedStyle에 대한 구현이 없는 모양이다. 그래서 jest-preset-angular에서는 setup-jest.ts에 다음과 같이 mocking을 해놨다. 위에서 발생한 에러를 쳐다보면 당연히 구현이 없으니 에러가 발생한다. 

Object.defineProperty(window, 'getComputedStyle', {
  value: () => ['-webkit-appearance'],
});

그래서 다음과 같이 mocking한 걸 변경한다. 

Object.defineProperty(window, 'getComputedStyle', {
  value: () => ({
      getPropertyValue: (prop) => {
          return '';
      }
  })
});

 

References

 


WRITTEN BY
HanDDol
여행이란 건 말이지. 첫 걸음을 내딜 때는 모든 게 낯설고.. 그리고 점점 더 낯선 세상에 익숙해지면서 세상의 모든 곳이 고향처럼 느껴진다. 고향으로 돌아오는 여행의 마지막 걸음에는 나의 고향이 더 이상 익숙한 곳이 아닌 낯선 곳임을 알게 된다.

,
m: expect.objectContaining({ type: ServerActions.InitialServersLoaded.type })

이 조합으로 검증을 하면 귀찮은 일이 많이 생긴다. 아래 같이 InitialServerLoaded Action을 발산할 때 Action에 Field가 여러 개고 랜덤으로 생성되는 값이면 아주 귀찮아진다. 

 

      it('무조건, IntialServersLoadedOk를 발산해야 한다', () => {
        jest.spyOn(metricsApiSvc, 'getServerMetric')
          .mockImplementation(() => of( mockResponse ));

        testScheduler.run(helpers => {
          const { cold, hot, expectObservable, expectSubscriptions, flush } = helpers;
          actions$ = hot('-a----a-', { a: ServerActions.InitialServersRequesting() });

          expectObservable(spectator.service.initialServersRequesting$).toBe(
            '-(ml)-l-',
            {
              l: ServerActions.InitialServersLoadedOk(),
              m: ServerActions.InitialServersLoaded({servers: {
              	id: 355232,
                desc: 'desc'
              })
            }
          );
        })
      });

RxJs Marble Testing를 Jest와 같이 사용하게 되면, Assert에 toEqual이 사용된다. 그리고 Jest에서는 다행히도 toEqual 내에서 사용 가능한 Matcher가 존재한다. 그중 하나가 expect.any이다. 그래서 다음과 같이 변경 가능하다. 

// ...

m: ServerActions.InitialServersLoaded({servers: {
	id: expect.any(number),
    desc: expect.any(string)
})

// ...

그런데 이것도 필드 수가 많아지면 피곤해진다. type만 비교하고 싶은데 말이지. 그럴 때는 다음과 같이.. 

m: expect.objectContaining({ type: ServerActions.InitialServersLoaded.type })

 


WRITTEN BY
HanDDol
여행이란 건 말이지. 첫 걸음을 내딜 때는 모든 게 낯설고.. 그리고 점점 더 낯선 세상에 익숙해지면서 세상의 모든 곳이 고향처럼 느껴진다. 고향으로 돌아오는 여행의 마지막 걸음에는 나의 고향이 더 이상 익숙한 곳이 아닌 낯선 곳임을 알게 된다.

,