지피지기 백전백퇴

웹 애플리케이션에서 구동하는 자바스크립트 코드 테스트 작성하기


뭐가 문제인데?

내가 개인적으로 만들어 쓰는 서비스는 크게 GraphQL을 제공하는 서버와 웹 애플리케이션으로 구성되어 있다. 게다가 API 없이는 돌아가는 기능이 거의 없는 웹 애플리케이션이다 보니, 대부분의 유용한 테스트가 서버 API를 바로 호출하는 식으로 구현되어 있다.

처음에는 서버 인증을 authentication 헤더로 처리했었는데, 이후에 공개키 기반 로그인을 구현하면서 대부분의 API에 대해 유저 인증을 붙이고, 대신 authentication 헤더 인증을 제거한 상황이다.

(실은 테스트 호출용 유저와 prod 유저를 구분하면 좋겠지만 그것조차 안되어있는 하꼬방 구현이다)

기능 구현은 어찌어찌 했는데, 테스트가 아주 번거로워지는 문제가 생겼다.

  1. CORS 요청이니까 서버에서 CORS allowed origin에 클라이언트 도메인을 추가해야 한다.
  2. 클라이언트에서는 서버에서 발급한 쿠키를 저장해야 한다.
  3. 서버 API를 호출하는 테스트는 모두 로그인 과정을 구현해야 한다.

테스트의 문제 - 로그인이 안돼!

분명 로그인 API를 호출하면 ‘로그인 성공’에 해당하는 응답을 받는데, whoami API를 호출하면 로그인이 안된 것으로 나온다.

뭐가 문제인데?

로그인 성공! 시점에 세션 관리를 위해 set-cookie가 같이 적용되는데, 클라이언트에서 이 쿠키를 무시하고 있다.

원래는 테스트 클라이언트 환경에서 cookie를 관리하도록 하는 방법이 있을텐데, 이게 원래는 브라우저에서 돌아가야 하는 코드인데 단지 테스트가 node.js에서 돌아간다고 해서 쿠키 관리를 테스트 코드에 대해서만 따로 적용하는게 왠지 기분나쁘더라구??!

어떻게 해결할래?

생각해보면 브라우저에서 구동하는 웹앱이니까, 테스트도 브라우저에서 돌리는게 맞지 않나? 어? Karma?

하지만 Karma는…

죽었어요

그래서 web test runner라는 녀석을 찍먹해보기로 했다.

테스트 환경에 web test runner 적용하기

원래 빌드 과정에서 typescript를 적절히 bundle하는 과정이 있는데, 이걸 테스트에 대해서도 동일하게 적용해야 브라우저에서 실행할 수 있는 모듈 코드가 나올 것이다.

글이 슬슬 길어지니 결론만 적어보자.

// Rollup을 써서 테스트 코드의 의존성을 한데 모아주자.
import { fromRollup } from '@web/dev-server-rollup';
import rollupCommonjs from '@rollup/plugin-commonjs';
import rollupNodeResolve from '@rollup/plugin-node-resolve';
import rollupTypescript from '@rollup/plugin-typescript';
import rollupReplace from '@rollup/plugin-replace';

// 브라우저에서 실행하니 commonjs가 편하다.
const commonjs = fromRollup(rollupCommonjs);
// 의존성은 node 모듈을 통해 찾으시오
const nodeResolve = fromRollup(rollupNodeResolve);
// 전체 코드가 typescript이니 이걸 써주자.
const typescript = fromRollup(rollupTypescript);
// React가 요상하게 process.env.NODE_ENV를 찾으니, 이걸 써서 대체해주자. 좀 hacky하지만 어차피 테스트용이니 뭐...
const replace = fromRollup(rollupReplace);

export default {
  files: 'src/**/*.spec.ts',
  nodeResolve: true,
  plugins: [
    replace({
      'process.env.NODE_ENV': JSON.stringify("production"),
    }),
    typescript({
      tsconfig: './tsconfig.json',
      sourceMap: true,
      inlineSources: true,
    }),
    nodeResolve({
      browser: true,
      preferBuiltins: false,
    }),
    commonjs(),
  ],
  testFramework: {
    config: {
      ui: 'bdd',
      timeout: '2000',
    },
  },
};

결론

이렇게 하고서도 처음엔 잘 안됐는데, 알고보니 서버에서 cookie를 반환할 때 SameSite 속성을 설정해줘야 했었다. None으로 설정해주니 CORS 환경에서도 잘 동작한다.