[Next.Js] with Testing

김서현·2023년 4월 21일
0
post-thumbnail

Next.js에서 일반적으로 사용되는 테스트 도구인 Cypress, PlaywrightJest and React Testing Library를 설정하는 방법을 알아보자.

우선 테스트 유형에 대해서 알아봅시다.

Unit Test(단위 테스트)란?

Unit 테스트는 작은 코드 조각 즉 함수 또는 메소드에 대해 테스트하는 것을 의미합니다. 예를 들어 애플리케이션에서 로그인 메소드에 대한 돌릭접인 테스트가 1개의 단위 테스트가 될 수 있습니다. 즉 단위 테스트는 애플리케이션을 구성하는 하나의 기능이 올바르게 작동하는지를 독립적으로 테스트하는 것이며 "이 기능을 실행하면 어떤 결과가 나온다"정도로만 테스트합니다. 이러한 테스트는 빠르게 테스트를 수행 할 수 있으며 각 컴포넌트의 무결성을 보장할 수 있습니다.

Unit Test의 문제점

소프트웨어의 구조가 명확하지 않고, 단일 컴포넌트의 기능이 자주 변경되는 환경에서는 테스트가 자주 변경되어야 하고 이는 테스트 작성의 효율을 떨어뜨리기 때문에 소프트웨어의 구조가 명확한 프로젝트에서 사용해야 합니다.

Integration Test(통합 테스트)란?

Integration 테스트는 통합된 기능들을 테스트하는 것입니다. Unit 테스트가 단일 컴포넌트를 기준으로 테스트 한다면, 두 개 이상의 Unit들 간의 데이터를 주고받는 환경을 테스트합니다. 이를 통해 연결에서 발생하는 에러를 검증할 수 있으며, 단위 테스트보다 비교적 넓은 범위에서 테스트하기에 리팩토링에 쉽게 깨지지 않는 장점이 있습니다.

E2E(End-to-End)란?

E2E 테스트는 Ent To End 테스트의 약자로 애플리케이션의 흐름을 처음부터 끝까지 테스트하는 것을 의미합니다. 이러한 테스트는 전체 앱이 응집력 있게 작동하는지 확인하는데 유용하게 사용됩니다. 유닛 테스트나 통합 테스트는 모듈의 무결성을 증명할 수 있지만, 모듈의 무결성이 애플리케이션 동작의 무결성까지 증명해 주지는 않습니다. 그래서 E2E 테스트는 실제 사용자의 시나리오를 테스트함으로써 애플리케이션 동작을 테스트하게 되고, 이 테스트를 통과함으로써 애플리케이션의 무결성을 증명할 수 있게 됩니다.

E2E의 문제점

사용자의 시나리오를 처음부터 끝까지 검증한다는 것은 그만큼 테스트 과정이 길고 복잡하다는 것을 의미합니다. 프로젝트의 규모가 클수록 테스트 실행 시간이 아주 오래 걸릴 수도 있다는 것입니다. 또한 네트워크 딜레이와 같은 랜덤 변수로 생기는 실패로 인해 테스트 신뢰도가 유닛과 통합에비해 낮습니다.
이와 관련해서 구글 테스트 자동화 컨퍼런스에서는 단계별 테스트의 비중과 비용에 대해서 잘 나타내주는 테스트 피라미드를 통해 기준을 제시했습니다.
Test Pyramid
테스트 비용에 따라 E2E테스트 10%, 통합 테스트 30%, 유닛 테스트 60%와 같이 비중을 조절해야합니다. 물론 이는 테스트를 관리하는데 사용되는 이론 중 하나일 뿐이지만 E2E 테스트는 비용이 많이 드는 만큼 꼭 필요한 테스트만 수행해야 한다는 사실에는 대부분 이견이 없는 것 같습니다.

Cypress

CypressE2E(End-to-End) 및 구성요소 테스트에서 사용되는 테스트 도구입니다.

Next에서 Cypress 적용하기

우선 cypress패키지를 설치합시다.

npm install --save-dev cypress

package.json의 scripts에 cypress를 추가합니다.

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "cypress": "cypress open",
}

설정을 마친 후 터미널에서 실행합니다.

npm run cypress

Cypress E2E 테스트

두개의 페이지로 예시를 들어보겠습니다.

// pages/index.js

import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <h1>Homepage</h1>
      <Link href="/about">About</Link>
    </nav>
  )
}
// pages/about.js

export default function About() {
  return (
    <div>
      <h1>About Page</h1>
      <Link href="/">Homepage</Link>
    </div>
  )
}

두 페이지가 올바르게 작동하는지 확인하는 설정을 추가합니다.

// cypress/e2e/app.cy.js

describe('Navigation', () => {
  it('should navigate to the about page', () => {
    // index페이지 주소
    cy.visit('http://localhost:3000/')

    // about을 href로 가진 a태그를 찾아서 클릭합니다.
    cy.get('a[href*="about"]').click()

    // 새롭게 이동한 url에 /about가 포함되는지 확인합니다.
    cy.url().should('include', '/about')

    // 새 페이지에 About Page 텍스트가 담진 h1태그가 포함되는지 확인합니다.
    cy.get('h1').contains('About Page')
  })
})

cypress.config.ts에 baseUrl을 추가하면 cy.visit("/")로 사용할 수 있습니다.

import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
  },
})

Cypress component 테스트

component 테스트는 unit 테스트라고도 합니다.

// pages/about.cy.js

import AboutPage from './about.js'

describe('<AboutPage />', () => {
  it('About페이지 렌더링 후 텍스트와 링크 확인', () => {
    // About페이지 마운트
    cy.mount(<AboutPage />)

    // 새 페이지에 About Page 텍스트가 담진 h1태그가 포함되는지 확인합니다.
    cy.get('h1').contains('About Page')

    // "/"를 href로 가진 a태그가 포함되는지 확인합니다.
    cy.get('a[href="/"]').should('be.visible')
  })
})

Playwright

Playwright는 단일 API로 Chromium, Firefox 및 Webkit을 자동화할 수 있는 테스트 프레임 워크입니다.
Playwirhgt도 모든 플랫폼에서 E2E 테스트 및 통합 테스트를 작성할 수 있습니다.

Next에서 Playwright 적용하기

먼저 @playwright/test패키지를 설치합니다

npm install --save-dev @playwright/test

package.json파일의 scripts에 추가합니다.

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "test:e2e": "playwright test",
}

Playwright E2E 테스트

Cypress때처럼 두 페이지를 만듭니다.

// pages/index.js

import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <Link href="/about">About</Link>
    </nav>
  )
}
// pages/about.js

export default function About() {
  return (
    <div>
      <h1>About Page</h1>
    </div>
  )
}

테스트 코드를 추가합니다.

// e2e/example.spec.ts

import { test, expect } from '@playwright/test'

test('should navigate to the about page', async ({ page }) => {
  // index 페이지에서 시작(cypress처럼 config 파일을 통해 baseURL 설정이 가능합니다.)
  await page.goto('http://localhost:3000/')
  // 텍스트가 About인 요소를 찾아 클릭합니다.
  await page.click('text=About')
  // 새 URL은 /about여야 합니다.
  await expect(page).toHaveURL('http://localhost:3000/about')
  // 새 페이지에는 About Page가 포함된 h1태그가 있어야 합니다.
  await expect(page.locator('h1')).toContainText('About Page')
})

Playwright는 실제 Next.js 애플리케이션을 테스트하므로 Playwright를 시작하기 전에 Next.js 서버를 먼저 실행해야 합니다.
num run buildnum run start를 통해 서버를 실행한 다음 npm run test:e2e를 다른 터미널에서 실행하여 Playwright를 실행할 수 있습니다.

Jest and React Testing Library

Jest와 React Testing Library는 Unit 테스트를 위해 자주 사용됩니다.
Next.js에서 Jest를 사용할 수 있는 방법은 세 가지가 있습니다.
1. CNA를 할 때 jest를 함께 설치하여 사용할 수 있습니다.
2. Next.js의 Rust Compiler를 통해 사용할 수 있습니다.
3. Babel를 통해 사용할 수 있습니다.
이제 이 세 가지 방법을 살펴봅시다.

Next에서 Jest 및 React Testing Library 적용하기

QuickStart with CNA

create-next-app을 통해 빠르게 Jest와 React Testing Library를 사용할 수 잇습니다.

npx create-next-app@latest --example with-jest with-jest-app

with the Rust Compiler

Next.js는 12버전부터 Jest의 기본 설정을 제공해줍니다.
Jest를 사용하기 위해 먼저 설치를 해봅시다.

npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

이제 jest.config.mjs를 프로젝트의 root 디렉터리에서 생성하고 다음 코드를 추가합니다.

// jest.config.mjs

import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  dir: './',
})

const config = {
  testEnvironment: 'jest-environment-jsdom',
}

export default createJestConfig(config)

테스트 환경에서 next.config.js.env.파일을 로드하려면 Next.js 앱의 경로를 제공해야 하므로 dir을 설정해줍니다. 그 후 createJestConfig를 이용하여 customJestConfig(config)를 생성합니다. config에는 setupFilesAfterEnvmoduleNameMapper의 기능도 있습니다.
Jest 문서에서 다양한 기능들을 확인해보세요.

with Babel

Jest를 Babel로 사용하기 위해서는 위에서 설치한 패키지 외에 babel-jest를 설치해야 합니다. 그 후 jest.setup.js파일을 설정합니다.

// jest.setup.js

import @testing-library/jest-dom;
// jest.config.js

module.exports = {
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
  setupFilesAfterEnv: ['./jest.setup.js'],
};

jest에게 "jest.setup.js파일을 환경에 넣어줘"라는 config.js 파일을 생성합니다. .next와 node_modules는 제외했습니다.

Jest 테스트

먼저 package.json 파일을 설정합시다. jest ``watch는 파일이 변경되면 테스트를 다시 실행합니다. 더 많은 옵션은 Jest 문서를 확인해주세요.

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "test": "jest --watch"
}

이제 프로젝트의 컴포넌트를 테스트 해봅시다.

// __tests__/index.test.jsx

import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
import '@testing-library/jest-dom'

describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />)

    const home = screen.getByText('Home');

    expect(home).toBeInTheDocument()
  })
})

마무리

주변에서 프론트엔드는 TDD가 어렵다는 말을 들은 적이 있습니다. 그러나 복잡한 프로젝트를 진행해보니 기존 코드를 리팩토링하는 것, 기존 코드에 새 기능을 추가하는 것 조차 어려웠습니다. 그래서 코드의 품질 향상과 코드 관리를 위해서 테스트가 필요하다고 느꼈고, 테스트의 기본 개념부터 다시 공부할 수 있어 좋았습니다. 이 글을 통해 저뿐만 아니라 저와 같은 고민을 하고 계신 분들도 많은 도움이 됬으면 좋겠습니다.

1개의 댓글

comment-user-thumbnail
2023년 5월 18일

와! 너무 머싯어여~!

답글 달기