Jest

ding·2024년 9월 30일

Jest는 페이스북에서 개발한 JavaScript 테스팅 프레임워크로, React application을 테스트할 때 많이 사용된다.

Jest의 장점

  • React와 최적의 궁합 : Jest는 React application을 위해 최적화되어있다.
  • 간단한 설정 : 다른 테스팅 프레임워크에 비해 설정이 간단하다.
  • 성능 : 테스트 속도를 높이기 위해 병렬로 테스트가 실행된다.

기본 구조

test suite

관련된 테스트 케이스를 그룹화하여 묶은 것. describe() 사용함.

test case

단일 테스트. test() 또는 it()으로 작성함.

describe('테스트 스위트 이름', () => {
    test('테스트 케이스 설명', () => {
        // 테스트 코드
    });

    it('또 다른 테스트 케이스 설명', () => {
        // 테스트 코드
    });
});

Matchers

일치 matcher

  • toBe() : 값이 정확히 일치하는지 확인
  • toEqual() : 객체나 배열 같은 복합 자료형의 구조가 일치하는지 확인.
  • not : 부정 matcher. 기대한 값이 아닌지 확인

truthy/falsy matcher

  • toBeNull() : 값이 null인지 확인
  • toBeUndefined() : 값이 undefined인지 확인
  • toBeDefined() : 값이 정의되어있는지 확인
  • toBeTruthy() : 값이 true로 평가되는지 확인
  • toBeFalsy() : 값이 false로 평가되는지 확인

숫자 관련 matcher

  • toBeGreaterThan() : 값이 비교값보다 큰지 확인
  • toBeGreaterThanOrEqual() : 값이 비교값보다 크거나 같은지 확인
  • toBeLessThan() : 값이 비교값보다 작은지 확인
  • toBeLessThanOrEqual() : 값이 비교값보다 작거나 같은지 확인

문자열 관련 matcher

  • toMatch() : 문자열이 특정 정규 표현식 또는 문자열과 일치하는지 확인

배열 관련 matcher

  • toContain() : 배열이나 반복 가능한 객체에 특정 항목이 포함되어있는지 확인

예외 처리

  • toThrow() : 함수가 예외를 발생시키는지 확인
function throwError() {
    throw new Error('Error message');
}

expect(throwError).toThrow('Error message');

비동기 코드 테스트

async/await

test('비동기 함수 테스트 with async/await', async () => {
    const data = await fetchData();
    expect(data).toBe('mocked data');
});

resolves/rejects

// 성공 테스트
test('프로미스가 성공적으로 해결됨', () => {
    return expect(fetchData()).resolves.toBe('mocked data');
});

// 실패 테스트
test('프로미스가 오류를 반환', () => {
    return expect(fetchError()).rejects.toThrow('error');
});

done 콜백

test('콜백 기반 비동기 코드 테스트', (done) => {
    fetchData((data) => {
        expect(data).toBe('mocked data');
        done();
    });
});

beforeAll()

모든 테스트 케이스가 실행되기 전에 한 번 실행된다. 보통 환경 설정이나 초기화 작업을 할 때 사용한다.
ex) DB 연결 설정, 서버 시작

afterAll()

모든 테스트 케이스가 끝난 후에 한 번 실행된다. 보통 정리 작업을 할 때 사용한다.
ex) DB 연결 종료, 파일 닫기

beforeEach()

각 테스트 케이스가 실행되기 전에 매번 실행된다. 테스트가 독립적으로 실행되도록 각 테스트 케이스에 필요한 초기 설정을 할 때 사용한다.
ex) 변수 초기화, DB 상태 재설정

afterEach()

각 테스트 케이스가 끝난 후 매번 실행된다. 테스트 후 정리 작업을 할 때 사용한다.
ex) 각 테스트 후 생성된 데이터 삭제

훅의 실행 순서 : beforeAll -> beforeEach -> test -> afterEach -> afterAll

describe('비동기 작업 테스트', () => {
    let connection;

    beforeAll(async () => {
        connection = await connectToDatabase();
    });

    afterAll(async () => {
        await connection.close();
    });

    beforeEach(async () => {
        await connection.clearData();
    });

    test('비동기 데이터 처리', async () => {
        await connection.addUser('John');
        const user = await connection.getUser('John');
        expect(user).toBeTruthy();
    });
});

Query

쿼리는 Testing Library에서 페이지의 요소를 찾는 데 제공하는 방법이다.

쿼리 : https://testing-library.com/docs/queries/about/#priority
'getByRole'의 role : https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role

screen query method

  • command
    • get: expect element to be in DOM
    • query: expect element not to be in DOM
    • find: expect element to appear async
  • [All]
    • (exclude) expect only one match
    • (include) expect more than one match
  • queryType
    • Role: most preferred
    • AltText: images
    • Text: display elements
    • Form elements: PlaceholderText, LabelText, DisplayValue

Mocking

jest.fn()

jest.fn()은 가짜 함수 생성하는 함수이다. 이 함수는 호출 횟수나 전달된 인수 등을 추적할 수 있다.

const mockFn = jest.fn();

// 함수 호출
mockFn('first call');
mockFn('second call');

// 호출 횟수 및 인수 추적
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('first call');

mock 함수에 반환값을 설정할 수도 있다.

  • mockReturnValue(value) : 리턴 값 지정
  • mockImplementation(value) : 모크 함수를 즉석으로 구현
  • mockResolvedValue(value)/mockRejectedValue(value) : 비동기 함수에서 resolve/reject 값을 받음
// 모든 호출에서 동일한 값 반환
const mockFn = jest.fn().mockReturnValue('default value');
expect(mockFn()).toBe('default value');

// 한 번만 특정 값을 반환하도록 설정
const mockFnOnce = jest.fn()
    .mockReturnValueOnce('first call')
    .mockReturnValueOnce('second call');

expect(mockFnOnce()).toBe('first call');
expect(mockFnOnce()).toBe('second call');

jest.mock()

fn함수는 개별적으로 하나하나씩 모킹 처리한다면, mock 함수는 그룹을 한꺼번에 모킹처리한다. 즉, 테스트 환경에서 특정 모듈을 모킹할 수 있도록 지원한다. 이 함수는 모듈이나 의존성을 가짜로 만들어서 실제 모듈을 호출하는 대신 테스트에서만 사용하는 가짜 모듈을 대신 사용할 수 있게 한다.

// 외부 API를 사용하는 모듈을 mock하여 실제 API 호출을 방지하고, 가짜 데이터를 반환하도록 한다.

// userService.js
export const getUser = () => {
    return fetch('/user').then(response => response.json());
};

// test.js
import { getUser } from './userService';

jest.mock('./userService', () => ({
    getUser: jest.fn().mockResolvedValue({ id: 1, name: 'John' })
}));

test('mocking getUser API', async () => {
    const user = await getUser();
    expect(user).toEqual({ id: 1, name: 'John' });
});
  • 외부 의존성 차단 : 실제 HTTP 요청이나 데이터베이스 호출과 같은 의존성 코드를 실행하지 않도록 방지할 수 있다.
  • 테스트 환경 제어 : 함수나 모듈의 특정 동작을 시뮬레이션하여 원하는 방식으로 제어할 수 있다.
  • 성능 최적화 : 실제로 시간이 오래 걸리거나 복잡한 작업을 수행하는 모듈을 테스트 환경에서는 간단한 mock으로 대체하여 테스트 속도를 높일 수 있다.

Test 방식

Unit Test

Unit Test는 소프트웨어의 가장 작은 단위를 독립적으로 테스트하는 과정이다. 작은 코드 조각이 올바르게 동작하는지 확인할 수 있다.

// 어떤 함수 add(a, b)가 두 숫자를 더해 제대로 반환하는지 테스트
test('adds 1 + 2 to equal 3', () => {
    expect(add(1, 2)).toBe(3);
});
  • 독립적 : 단위 테스트는 외부 환경에 의존하지 않고 테스트하려는 코드 자체에 집중한다.
  • 빠른 피드백 : 작은 단위의 테스트이기 때문에 실행 속도가 빠르고, 오류가 발생하는 경우 즉시 피드백을 받을 수 있다.
  • 디버깅 : 오류가 발생하면 원인을 쉽게 파악할 수 있다.

Integration Test

통합 테스트는 여러 유닛이 함께 작동하는 방식을 테스트해서 유닛 간의 상호작용을 테스트한다.
여러 모듈이나 클래스 간의 상호작용을 포함하고, 데이터베이스, API, 파일 시스템 등과의 상호작용을 포함하기도 한다.

// 두 개의 모듈 userService와 authService가 잘 협력해서 사용자 인증을 처리하는지 확인
test('authenticates a user through userService and authService', () => {
    const user = userService.getUser(1);
    const isAuthenticated = authService.authenticate(user);
    expect(isAuthenticated).toBe(true);
});

Functional Test

기능 테스트는 소프트웨어의 특정 기능을 테스트한다.

// 로그인 페이지에서 올바른 사용자 정보를 입력하면 시스템이 대시보드로 리디렉션 되는지 확인
test('allows a user to log in and redirects to dashboard', () => {
    const loginPage = new LoginPage();
    loginPage.enterCredentials('username', 'password');
    loginPage.submit();
    expect(browser.getCurrentUrl()).toBe('/dashboard');
});

End to End(E2E) Test

애플리케이션의 전체 흐름을 실제 사용자의 시나리오처럼 테스트하여 시스템이 처음부터 끝까지 문제 없이 동작하는지 확인한다. 브라우저 상에서 user interaction을 시뮬레이션하여 프론트엔드, 백엔드, 데이터베이스를 전부 테스트한다.

// 사용자가 로그인한 후 대시보드에서 제품을 검색하고 장바구니에 넣고 결제하는 전체 과정을 시뮬레이션
describe('E2E - Shopping Cart Flow', () => {
    it('should allow a user to search for a product, add it to the cart, and checkout', () => {
        cy.visit('/login');
        cy.get('input[name="username"]').type('user1');
        cy.get('input[name="password"]').type('password123');
        cy.get('button[type="submit"]').click();

        cy.url().should('include', '/dashboard');
        cy.get('input[name="search"]').type('laptop');
        cy.get('button[type="submit"]').click();

        cy.get('.product-list').contains('Laptop').click();
        cy.get('.add-to-cart').click();
        cy.get('.checkout').click();

        cy.url().should('include', '/order-confirmation');
    });
});

0개의 댓글