front-end Test

도입부

행복한 가정은 서로 닮았지만 불행한 가정은 모두 저마다의 이유로 불행하다 [톨스토이 - 안나 카레니나, 2011 번역 - 펭귄클래식코리아]

하나의 문제는 하나의 원인으로 이루어 지지 않는다

테스트는 deterministic 해야한다. (언제 실행되든 항상 같은 결과를 내야한다.)

issue

[Error: This browser does not support 'ResizeObserver' out of the box. Please provide a polyfill as a prop.]

  • 해결 import 'resize-observer-polyfill/dist/ResizeObserver.global'

관련 글들

정리 - 노트 클립 - 구글은 어떻게 소프트웨어를 테스트하는가

toast UI: 테스트

  • 단위 테스트
    • 주로 모듈 단위
  • 통합 테스트
    • 두 개 이상의 모듈이 연결된 상태를 테스트
  • E2E 테스트

    • 실제 사용자의 관점에서 테스트를 진행하며, 그런 의미에서 기능테스트 혹은 UI 테스트라고 불리기도 한다
    • 테스트 코드가 실제 코드 내부 구조에 영향을 받지 않기 때문에 큰 범위의 리팩토링에도 깨지지 않으며, 이를 통해 개발자들이 좀 더 자신감 있게 코드를 개선할 수 있도록 도와준다
    • 테스트 작성하기 어렵다
    • 테스트 사이의 중복이 발생한다
    • 예상 밖의 문제들(네트워크 오류, 프로세스 대기로 인한 타임아웃)으로 인해 테스트가 가끔 실패하는 일이 발생
  • 멀티 브라우저 테스트 환경 구축하기
  • Selenium WebDriver

    • WebDriver는 기본적으로 브라우저가 서버 역할을 하고 제어를 요청하는 기기(개발자 PC 혹은 CI 서버)가 클라이언트의 역할을 하는 서버-클라이언트 구조라고 할 수 있으며, 브라우저용 드라이버와 개발자용 클라이언트를 설치해서 사용하게 된다.

    • E2E 테스트의 단점을 그대로 갖고 있기 때문에 테스트를 작성하거나 유지 보수하는데 많은 비용이 들며, 이로 인해 개발자들이 개발 단계에서 사용하는 테스트 도구로써는 사실상 널리 활용되지 못하고 있다.

    • 프레임워크

      • Protractor : Angular 프로젝트를 위한 테스트 프레임워크
      • WebdriverJS : Selenium Webdriver의 정식 Node.js 구현체이며, 낮은 수준(Low-level)의 API를 제공한다.
      • NightWatch : Mocha 기반의 테스트 러너와 직관적인 API, CI 서버 통합 등의 다양한 기능을 지원한다.
      • WebdriverIO : 테스트 러너, 정적 웹 서버, CI 서버 통합, REPL 인터페이스 등 다양한 기능을 지원하며 커뮤니티가 가장 잘 활성화되어 있다.
  • Cypress
    • Cypress는 Selenium WebDriver와는 전혀 다른 목적을 갖는 도구이며, 정확히 프론트엔드 개발자들이 개발 단계에서 사용하기에 최적화된 도구라고 할 수 있다
    • WebDriver와는 다르게 실제 애플리케이션과 테스트 코드를 동일한 브라우저에서 실행하는 방식을 취하고 있다
    • 브라우저 기반의 GUI를 사용하여 테스트의 실행 상태를 확인하고 디버깅할 수 있는 다양한 편의 기능을 제공한다.
      • 실행된 모든 테스트 명령과 각 명령이 실행될 때의 UI 상태를 스냅샷 형태로 모두 저장하고 있어, 특정 시점의 UI 상태를 눈으로 확인할 수 있다
      • 또한 전체 테스트 진행 과정을 동영상으로 저장하거나 테스트가 실패했을 때 자동으로 스크린샷을 남길 수 있어 테스트가 실패했을 때 원인을 파악하기가 매우 쉽다
      • 게다가 브라우저에서 실행되기 때문에 필요한 경우 크롬 개발자 도구를 사용해 디버깅을 할 수도 있다.
    • 브라우저 내부에서 실행되는 방식에는 단점
      • 브라우저의 새 탭 혹은 새 창을 열 수 없으며
      • 동일 출처(Same-origin) 정책을 벗어나는 페이지로는 이동을 할 수가 없다

용어 정리

  • TDD: test driven development
  • BDD: Behaviour-Driven Development
    • 어떤 행위를 해야한다. (should do someting)
    • BDD는 비즈니스 요구사항에 집중하여 테스트 케이스

경우에 관한 테스트 생각 by toyjhlee

  • lib
    • lib 를 믿어라. input 이 올바른지 확인!

Test Runner Test Mathcher Test Mock

Libaray

reat-testing-library

api-queries

cheatsheet

testing-library / jest-dom - Custom matchers

examples

React Unit Test — react-testing-library, 너를 알아가는 시간

  • Enzyme 에서 react-testing-library 변경 관련

ReferenceError: document is not defined

  • message

    The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/en/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.
    
    ReferenceError: document is not defined
    
  • 해결방법

    1. testEnvironment 를 node -> jsdom 으로 변경 in package.json
    2. jest 재 실행

Property 'toBeInTheDocument' does not exist on type 'Matchers<any>

  • message
    Property 'toBeInTheDocument' does not exist on type 'JestMatchersShape<HTMLElement[], Matchers<void, HTMLElement[]>, Matchers<Promise<void>, HTMLElement[]>>'.
    
  • 해결방법

    • [Property ‘toBeInTheDocument’ does not exist on type ‘Matchers'](https://stackoverflow.com/questions/57861187/property-tobeinthedocument-does-not-exist-on-type-matchersany)

    • import '@testing-library/jest-dom/extend-expect

jest

daleseo.com

snapshot-testing

  • 왜 스냅 샷 테스트인가?
    • 스냅 샷 통합 테스트가 해결하는 엔드 투 엔드 테스트에서 많은 문제를 발견했습니다.
      • 스냅 샷 테스트가 시각적 회귀 테스트 대비 장점
        • 결함 없음
        • 빠른 반복 속도
        • 디버깅
  • -u 플래그를 사용하여 스냅 샷을 다시 생성 할 수도 있습니다
  • toMatchInlineSnapshot
    • 스냅 샷 값이 소스 코드에 자동으로 다시 기록된다는 점을 제외하면 외부 스냅 샷 ( 파일) 과 동일하게 작동합니다 . 즉, 올바른 값이 기록되었는지 확인하기 위해 외부 파일로 전환하지 않고도 자동으로 생성 된 스냅 샷의 이점을 얻을 수 있습니다.

Testing Sass with Jest

  • Testing that your Sass compiles without errors
  • Enforcing good Sass hygiene
  • Testing Sass functions
  • Testing Sass mixins

sass file 의 내용을 테스트 한다

// root.module.scss
:root {
}
const util = require('util')
const sass = require('node-sass')
const sassRenderPromisify = util.promisify(sass.render)

export const sassRender = (options: any) => {
    return sassRenderPromisify({
        // Where node-sass should look for files when you use @import
        includePaths: ['./sass'],

        // Using a compact output style allows you to use concise 'expected' CSS
        outputStyle: 'compact',

        // Merge in any other options you pass when calling render
        ...options,
    })
}
it('', async () => {
    const rootModule = await sassRender({
        file: 'src/root.module.scss',
    })

    expect(rootModule.css.toString()).toContain(':root {')
})

Jest — How to Use Extend with TypeScript

declare global {
    namespace jest {
        interface Matchers<R> {
            withOut(expected: any): R
        }
    }
}

expect.extend({
    withOut(actual: any, expected: any) {
        const pass = actual % expected === 0
        const message = pass
            ? () => `expected ${actual} not to be without by ${expected}`
            : () => `expected ${actual} to be without by ${expected}`

        return {message, pass}
    }
})

canvas mock

// setupTests.ts
import 'jest-canvas-mock'

getContext mock

참고

function mockCanvas(window) {
    window.HTMLCanvasElement.prototype.getContext = function () {
        return {
            fillRect: function () {},
            clearRect: function () {},
            getImageData: function (x, y, w, h) {
                return {
                    data: new Array(w * h * 4),
                }
            },
            putImageData: function () {},
            createImageData: function () {
                return []
            },
            setTransform: function () {},
            drawImage: function () {},
            save: function () {},
            fillText: function () {},
            restore: function () {},
            beginPath: function () {},
            moveTo: function () {},
            lineTo: function () {},
            closePath: function () {},
            stroke: function () {},
            translate: function () {},
            scale: function () {},
            rotate: function () {},
            arc: function () {},
            fill: function () {},
            measureText: function () {
                return {width: 0}
            },
            transform: function () {},
            rect: function () {},
            clip: function () {},
        }
    }

    window.HTMLCanvasElement.prototype.toDataURL = function () {
        return ''
    }
}
const document = jsdom.jsdom(undefined, {
    virtualConsole: jsdom.createVirtualConsole().sendTo(console),
})

const window = document.defaultView
mockCanvas(window)

worker mock

참고

대상 코드

import ExternalSourceLoaderWorker from "comlink-loader!./ExternalSourceLoaderWorker"; /

mock 코드

jest.mock('../worker', () => {
    return jest.fn().mockImplementation(() => ({
        ExternalSourceLoaderWorker: jest.fn(),
    }))
})

private method mock

class Foo {
    private prop: string;
}

const foo = new Foo()
jest.spyOn<any, string>(foo, 'prop');

Testing Sass with Jest

참고

toHaveStyleRule 에 ruls 이 많은 경우

const toHaveStyleRules = (component, property, options) => {
    let hyphen = ''
    _.each(property, (value, key) => {
        hyphen = key.replace(/([A-Z])/g, g => `-${g[0].toLowerCase()}`)
        expect(component).toHaveStyleRule(hyphen, value, options)
    })
}

window 에 값 설정

jest 실행 시 내부의 class 가 window 의 지정한 property 를 참조하는 경우 아래 방법으로는 undefind 가 발행한다

Object.defineProperty(window, key, {
    value: {},
})
Object.defineProperties(window, {
    key: {
        value: {},
    },
})

아래 방법으로 해야 한다

// setupTests
window.name = {}

Timer Mocks

jest.advanceTimersByTime(1000);

7 Ways to Debug Jest Tests in Terminal

  1. The Standard Way
  2. Without an Initial Break
  3. Debugging TypeScript Tests
  4. Best Way To Debug ™️
  5. What About CRA?
  6. Debugging From the Command-Line
  7. All-In-One solution - ndb

visual regression testing(시각적 회귀 테스트)

E2E

robinwieruch

form 관련 예시 및 설명

Difference between enzyme, ReactTestUtils and react-testing-library

Redux

분류 전

링크들