스벨트킷 블로그 단위 테스트하기

스벨트킷으로 블로그 만들기 글과 이어지는 내용입니다. 이 글에서는 이렇게 만들어진 블로그에 단위 테스트를 적용하는 방법을 설명합니다.

단위 테스트란

단위 테스트unit test는 소프트웨어의 개별 모듈들이 의도한 방식대로 작동하는지 확인하는 절차입니다. 과거에는 사람이 수동으로 테스트를 했지만 요즘에는 자동화된 단위 테스트automated unit test 코드를 만들어서 실행하는 방식을 더 선호하며, 특별히 언급하는 경우를 빼면 ‘단위 테스트’는 모두 ‘자동화된 단위 테스트’를 말합니다.

타입스크립트 단위 테스트

스벨트킷은 vite를 사용하기 때문에 단위 테스트 프레임워크로 vitest를 사용하면 호환이 잘 되어서 편리합니다.

npm i -D vitest

이제 프로젝트 루트 디렉터리에 있는 vite.config.js에 테스트 관련 설정을 추가해줍니다.

const config = {
  // ... 생략
  test: {
    globals: true,
    environment: 'jsdom',
  },
}

그리고 package.json에 테스트 스크립트를 추가해줍니다.

{
  "scripts": {
    "test": "vitest --run",
    "test:watch": "vitest"
  }
}

npm run test를 입력하면 테스트가 1회 실행되고, npm run test:watch를 실행하면 파일이 바뀌면 자동으로 테스트를 실행해줍니다. watch 모드는 특히 테스트 주도 개발test-driven development을 할 때 좋습니다.

다만, 아직 테스트 케이스가 없기 때문에 에러가 납니다. 환경 설정이 끝났으니 테스트를 추가할 차례입니다. 마침 마크다운 파일에서 메타데이터를 추출하는 코드가 있는데, 이 코드에 대한 테스트를 붙여보면 좋겠습니다.

다음은 src/lib/server/article.ts의 일부입니다.

export function extractMeta(id: string, markdown: string, mtime: Date): ArticleMeta {
  const S = '---\n' // separator
  const raw = markdown.substring(S.length, markdown.indexOf(S, S.length)).trim()
  const frontmatter = yaml.load(raw) as Record<string, any>
  return {
    ...frontmatter,
    id,
    draft: !!frontmatter.draft,
    modifiedAt: mtime.toISOString(),
  } as ArticleMeta
}

이제 위 코드를 테스트하는 src/lib/server/article.test.ts 파일을 추가하겠습니다.

import dedent from 'dedent'
import { describe, expect, test } from 'vitest'

import { type ArticleMeta, extractMeta } from './article'

describe('extractMeta()', () => {
  test('Basic', () => {
    const md = dedent`
      ---
      title: TITLE
      summary: SUMMARY
      publishedAt: '2022-04-01'
      draft: true
      ---
      Hello
    `
    const actual = extractMeta('ID', md, new Date('2022-04-01T00:00:00.000Z'))
    const expected: ArticleMeta = {
      id: 'ID',
      title: 'TITLE',
      summary: 'SUMMARY',
      publishedAt: '2022-04-01',
      modifiedAt: '2022-04-01T00:00:00.000Z',
      draft: true,
    }
    expect(actual).toEqual(expected)
  })

  test('"draft" should be false if omitted', () => {
    const md = dedent`
      ---
      title: TITLE
      summary: SUMMARY
      publishedAt: '2022-04-01'
      ---
      Hello
    `
    expect(extractMeta('ID', md, new Date('2022-04-01T00:00:00.000Z')).draft).toBe(false)
  })
})

위 테스트에서는 dedent라는 태그드 템플릿tagged template을 사용하고 있습니다. 자바스크립트에서 멀티라인 문자열을 깔끔하게 쓸 수 있게 해주는 라이브러리입니다. 아래 명령으로 설치하시면 됩니다.

npm i -D dedent

이제 npm run test을 실행하면 1개의 테스트 파일에 담긴 2개의 테스트 케이스가 통과했다고 나옵니다.

마크다운 문서 단위 테스트

마크다운 문서는 어떻게 테스트를 하면 좋을까요? mdsvex 라이브러리를 설정했기 때문에 *.md 파일도 일반 스벨트킷 컴포넌트와 동일한 방법으로 불러올 수 있습니다.

이를 위해 @testing-library/svelte를 설치합니다.

npm i -D @testing-library/svelte

그런데 마크다운 문서를 테스트할 필요가 있을까요? 보통은 굳이 테스트할 필요가 없습니다. 다만 몇 가지 상황에서는 테스트가 유용할 수 있습니다.

이 글에서는 레이아웃을 테스트하고자 합니다. 마크다운의 프론트메터frontmatter에 적혀있는 titlesummary가 제대로 렌더링되는지 확인하면 좋겠습니다.

모든 문서에 중복된 테스트를 만들 필요는 없으니 하나의 문서에 대한 테스트만 만들면 좋겠습니다. 다음은 /routes/posts/about/+page.md 파일의 앞 부분입니다.

---
title: 소개
publishedAt: '2022-03-31'
summary: 블로그 소개
---

공부하고 정리하는 블로그입니다. 주로 프로그래밍 이야기를 합니다.

이 문서를 렌더링하면 다음과 같이 되어야 합니다.

아래와 같이 /routes/posts/about/test.ts 파일을 만들면 이 테스트를 수행할 수 있습니다.

import { cleanup } from '@testing-library/svelte'
import { beforeEach, expect, test } from 'vitest'

import { getQueryAPI } from '$lib/utils'

// @ts-ignore: *.md is not supported by typescript
import About from './+page.md'

beforeEach(cleanup)

test('Metadata rendering', () => {
  const $ = getQueryAPI(About)
  expect($('title')?.textContent).toBe('소개 :: 공부왕 김공부')
  expect($('meta[name="description"]')?.getAttribute('content')).toBe('블로그 소개')
  expect($('h1')?.textContent).toBe('소개')
})

다음은 마크다운 문서 또는 스벨트 컴포넌트에 대한 테스트를 도와주는 작은 유틸리티입니다. src/lib/utils.ts 라는 이름으로 저장하세요.

import { render } from '@testing-library/svelte'
import type { SvelteComponent } from 'svelte'

export const getQueryAPI = (c: typeof SvelteComponent) => {
  const doc = render(c).container.ownerDocument
  return (selector: string) => doc.querySelector(selector)
}

마지막으로, vitest는 기본적으로 *.test.ts 형식의 파일을 테스트로 인식하는데, 우리가 만든 테스트 파일은 test.ts이므로 이 형식에 부합하지 않습니다. 따라서 vite.config.js 파일을 수정하여 test.ts 형식도 인식하게 해줍니다.

// ...중략
const config = {
  // ...중략
  test: {
    // ...중략
    include: [
      // 기본 패턴
      '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
      // 추가된 패턴
      '**/{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
    ],
  },
}
// ...후략

스벨트 컴포넌트 단위 테스트

마크다운 문서의 단위 테스트와 동일하므로 설명을 생략합니다. 테스트 코드 예시가 필요하신 분은 Svelte Testing Library를 참고해주세요.

마치며

이 글에서 설명한 코드는 이 블로그에도 적용되어 있습니다. 소스 코드가 공개되어 있으니 참고해주세요.

관련 글