업로드 페이지에서 Cypress를 사용한 E2E 테스트

김선은·2024년 7월 15일
0

Cypress로 업로드 페이지 테스트하기

Next.js 환경에서 개발한 업로드 페이지에 Cypress를 이용해 E2E(End-to-End) 테스트를 구현했습니다.

이 테스트의 주요 목표는 업로드 페이지의 기본 렌더링부터 사진 등록, 장소 검색, 설명 입력, 그리고 최종적으로 등록 버튼이 활성화되는지 확인하는 전체 작업 흐름을 검증하는 것입니다.

테스트 코드를 적용하기 전 유저의 플로우를 예상해보고 정리했습니다.

테스트코드 적용 미리보기

1. 업로드 페이지의 모든 필드 입력과 버튼 활성화 확인하기

  • 업로드 페이지에서 모든 필드를 올바르게 입력하면, 등록 버튼이 활성화되는지를 확인합니다.
  • Cypress 테스트 환경에서는 실제로 이미지를 업로드하는 대신, img 태그가 생성되어 미리보기가 올바르게 표시되는지만 확인합니다.
  • 이 테스트는 사용자가 모든 필드를 입력했을 때만 폼이 제출 가능하도록 보장합니다.
describe('업로드 페이지의 기본 렌더링 및 입력과 버튼 검증 확인하기', () => {
  it('사진 등록, 검색 모달창 입력, 설명 입력하면 등록 버튼이 활성화 된다.', () => {
    cy.visit('/place/upload')

    // 제목과 Form 렌더링 확인하기
    cy.get('[data-cy="upload-title"]')
    cy.get('form').should('be.visible')

    // 사진 등록 시 미리보기가 보인다.
    const imagePath = 'testimage.PNG' // cypress/fixtures에 정적 이미지 추가
    cy.get('input[type="file"]').attachFile(imagePath)
    cy.get('img').should('be.visible')

    // 검색 모달창에서 입력하고 그 결과를 선택해서 업로드 폼에 다시 돌아온다.
    cy.get('[data-cy="right-arrow-icon"]').click()
    cy.get('[data-cy="search-modal"]').should('be.visible')
    cy.get('[data-cy="search-modal-input"]').type('카페베네')
    cy.get('[data-cy="search-modal-item"]').first().click()
    cy.get('[data-cy="upload-form-input"]').should('have.value', '카페베네')

    // 꿀플 노트 입력
    cy.get('[data-cy="upload-form-textarea"]').type(
      '정말 멋진 카페였습니다. 분위기도 좋고 커피도 맛있어요.'
    )

    // fetch 요청 모킹
    cy.intercept('POST', '/api/create-notification', {
      statusCode: 200,
      body: { success: true },
    }).as('createNotification')

    // 등록 버튼이 활성화된다.
    cy.get('[data-cy="upload-btn"]').should('not.be.disabled')
  })
})

2. 다녀온 플레이스 이후의 페이지 이동 확인하기

사용자가 다녀온 플레이스를 입력하기 위해 검색 모달창을 열고, 새로운 플레이스를 추가하는 과정을 테스트합니다.

  • 다녀온 플레이스를 입력하려면 검색 모달창이 뜹니다.

  • 그 이후 새로운 플레이스를 추가하면 newplacemap 페이지를 거쳐서 upload 페이지로 돌아옵니다.

  • 이 테스트는 새로운 플레이스를 추가하는 복잡한 과정을 검증하기 위해 깊이가 3번 이상 되는 단계를 포함합니다.

  • 단계별로 페이지가 이동하는지, 입력한 값이 올바르게 반영되는지를 확인하여 기능의 정상 동작을 보장합니다.

// describe 생략
it('모달창에서 새로운 꿀플레이스 추가하기를 누른 후 단계별 페이지 이동을 거쳐서 업로드 페이지로 돌아온다.', () => {
    cy.visit('/place/upload')

    // 모달창 띄우기
    cy.get('[data-cy="right-arrow-icon"]').click()
    cy.get('[data-cy="search-modal"]').should('be.visible')
    cy.get('[data-cy="search-modal-input"]').type('새로운 가게')
    cy.get('[data-cy="add-new-place-btn"]').click()

    // 새로운 꿀플레이스 추가 페이지로 이동 확인
    cy.url().should('include', '/place/newplace')
    cy.get('[data-cy="newplace-name-input"]').should('have.value', '새로운 가게')

    // 지도 페이지로 이동
    cy.get('[data-cy="map-page-link"]').click()
    cy.url().should('include', '/place/map')

    // 지도 페이지에서 기본 주소 확인 및 위치 등록 (초기 주소값)
    cy.get('p').contains('서울특별시 중구 세종대로 110').should('be.visible')
    cy.get('[data-cy="map-btn"]').click()

    // 새로운 꿀플레이스 추가 페이지로 돌아옴
    cy.url().should('include', '/place/newplace')
    cy.get('[data-cy="newplace-name-input"]').should('have.value', '새로운 가게')
    cy.get('[data-cy="newplace-address-input"]').should(
      'have.value',
      '서울특별시 중구 세종대로 110'
    )

    // 업로드 페이지로 이동
    cy.get('[data-cy="upload-page-link"]').click()
    cy.url().should('include', '/place/upload')

    // 업로드 폼에서 입력된 장소 확인
    cy.get('[data-cy="upload-form-input"]').should('have.value', '새로운 가게')
  })

주요 테스트 시나리오

첫번째 테스트

기본 렌더링 확인

  • 업로드 페이지가 정상적으로 로드되고, 제목과 폼이 올바르게 렌더링되는지 확인합니다.

사진 등록 및 미리보기 확인

  • 사용자가 사진을 업로드하면 미리보기가 정상적으로 표시되는지 확인합니다.

장소 검색 및 선택

  • 검색 모달창에서 장소를 검색하고 선택한 결과가 업로드 폼에 반영되는지 확인합니다.

설명 입력 및 등록 버튼 활성화

  • 설명을 입력한 후, 모든 필드가 올바르게 입력되면 등록 버튼이 활성화되는지 확인합니다.

두번째 테스트

  • 검색 모달창에서 새로운 플레이스 등록 과정 확인
  • newplace 페이지 이동
  • map 페이지 이동 후 다시 newplace 이동
  • 확인하기 누르면 업로드 페이지로 이동

참고사항

보통 Cypress 테스트를 작성할 때는 각 기능을 개별적으로 테스트하기 위해 여러 개의 it 블록으로 나눕니다.

예시코드

describe('카운터 앱', () => {
	// 첫 번째 테스트 시나리오
	it('페이지에 진입하면 카운터 앱이 정상적으로 실행된다(0이 표시된다)', () => {
		cy.visit('http://localhost:3000');
		cy.get('[data-cy=counter]').contains(0);
	});

	// 두 번째 테스트 시나리오
	it('플러스 버튼을 누르면 카운터가 1이 증가한다', () => {
		cy.visit('http://localhost:3000');
		cy.get('[data-cy=add-button]').click();
		cy.get('[data-cy=counter]').contains(1);
	});

	// 세 번째 테스트 시나리오
	it('마이너스 버튼을 누르면 카운터가 1이 감소한다', () => {
		cy.visit('http://localhost:3000');
		cy.get('[data-cy=minus-button]').click();
		cy.get('[data-cy=counter]').contains(-1);
	});
});

본 업로드 페이지 테스트 코드는 전체 플로우를 검증하기 위해서 입력 상태가 유지되어야 하므로 하나의 it 블록에 통합되어 있습니다.

1. Cypress 설치하기

Cypress 패키지 다운로드

npm install --save-dev cypress

package.json에 스크립트 추가

{
  "scripts": {
    "test": "cypress open"
  }
}

터미널에서 명령어를 작성합니다.

npm test 또는
npm t

프로그램이 켜지면 E2E Testing을 선택하고 Chrome을 선택합니다.

cypress.config.ts와 cypress 폴더가 생성됩니다.

Cypress 설정 파일 수정

import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
  },
});
  • cypress.config.ts에서
    baseUrl: 'http://localhost:3000'를 추가하여 기본 URL을 설정합니다.

2. Cypress 테스트 파일 설정

Cypress 폴더 구조 설정

  • cypress/e2e 폴더를 생성하고, upload.cy.ts 파일을 생성합니다.

테스트 시나리오와 코드 작성

import 'cypress-file-upload'

describe('업로드 페이지의 기본 렌더링 및 입력과 버튼 검증 확인하기', () => {
  it('사진 등록, 검색 모달창 입력, 설명 입력 후 등록 버튼을 누르면 홈으로 이동한다.', () => {
    cy.visit('/place/upload')

    // 제목과 Form 렌더링 확인하기
    cy.get('[data-cy="upload-title"]')
    cy.get('form').should('be.visible')

    // 사진 등록 시 미리보기가 보인다.
    const imagePath = 'testimage.PNG' // cypress/fixtures에 정적 이미지 추가
    cy.get('input[type="file"]').attachFile(imagePath)
    cy.get('img').should('be.visible')

    // 검색 모달창에서 입력하고 그 결과를 선택해서 업로드 폼에 다시 돌아온다.
    cy.get('[data-cy="right-arrow-icon"]').click()
    cy.get('[data-cy="search-modal"]').should('be.visible')
    cy.get('[data-cy="search-modal-input"]').type('카페베네')
    cy.get('[data-cy="search-modal-item"]').first().click()
    cy.get('[data-cy="upload-form-input"]').should('have.value', '카페베네')

    // 꿀플 노트 입력
    cy.get('[data-cy="upload-form-textarea"]').type(
      '정말 멋진 카페였습니다. 분위기도 좋고 커피도 맛있어요.'
    )

    // fetch 요청 모킹
    cy.intercept('POST', '/api/create-notification', {
      statusCode: 200,
      body: { success: true },
    }).as('createNotification')

    // 등록 버튼이 활성화된다.
    cy.get('[data-cy="upload-btn"]').should('not.be.disabled')
  })
})
  • 가져오려는 태그 요소에 이름을 지정합니다.
  • <h2 data-cy='upload-title'>
  • cy.get('[data-cy="upload-title"]')으로 요소를 가져옵니다.

이미지 등록 시 미리보기 확인

Cypress를 사용하여 파일 업로드를 테스트할 때 attachFile 메서드를 사용할 수 있습니다. 이 메서드는 cypress-file-upload 패키지를 통해 제공되며, 사용자가 파일을 업로드하는 시나리오를 자동화할 수 있게 해줍니다.

npm install --save-dev cypress-file-upload

패키지를 다운로드 합니다.

import 'cypress-file-upload';

테스트 코드 작성 상단에서 import 합니다.

const imagePath = 'testimage.PNG'
// cypress/fixtures 폴더에 위치한 파일
    cy.get('input[type="file"]').attachFile(imagePath)
 cy.get('img').should('be.visible')

파일 업로드를 테스트하기 위해 attachFile 메서드를 사용합니다. 이 메서드는 파일 인풋 요소에 파일을 첨부하는 기능을 제공합니다.

attachFile 메서드 설명

  • 테스트용 정적 이미지 등록: cypress/fixtures/testimage.PNG 파일을 등록하고 attachFile의 인수로 사용합니다.

  • 메서드 사용: cy.get('input[type="file"]').attachFile(imagePath);는 파일 인풋 요소에 지정된 파일을 첨부합니다.

  • 미리보기 확인: 파일이 첨부된 후, cy.get('img').should('be.visible');을 통해 업로드된 이미지의 미리보기가 정상적으로 표시되는지 확인합니다.

Firebase API 모킹

Firebase API 함수에 환경 변수를 사용하여 테스트 환경에서 실제 DB 호출을 피하는 방법입니다.
제 프로젝트의 업로드 페이지는 등록 버튼 시 로그인을 확인하기에 실제로 등록 버튼을 누르는 테스트는 생략하고, 모든 필드가 입력되었을 때 등록 버튼이 활성화 되는 상태만 확인했습니다.

Firebase API 모킹을 위해 추가했던 부분

1. cross-env 설치:

npm install --save-dev cross-env

2. package.json 수정:

{
  "scripts": {
    "test": "cross-env NODE_ENV=test cypress open"
  }
}
  • 환경 변수를 설정합니다.

3. Firebase 업로드 함수 수정

// firebase strage에 데이터를 등록하는 addHoneyPlace 함수
import { addDoc, collection } from 'firebase/firestore'
import { db } from '@root/firebase'
import { uploadNewPlace } from '@/interfaces/IPlace'

const isTestEnv = process.env.NODE_ENV === 'test'

export const addHoneyPlace = async (newPlace: uploadNewPlace) => {
  if (isTestEnv) {
    return 
    // 테스트 환경에서는 Firebase 호출 X
  }

  const placeDocRef = collection(db, 'honey_place')
  return await addDoc(placeDocRef, newPlace)
}

------------------------------------
// 업로드 페이지의 등록 버튼 함수
const onSubmit: SubmitHandler<FieldValues> = async (formData) => {
	const newPlace = {
        name,
        description,
        address,
        images: uploadedImageFiles,
        createdAt: new Date(),
    }

    await addHoneyPlace(newPlace)
}

에러사항

버튼을 못찾는 상황

button 태그가 아니라 만든 Button 컴포넌트여서.

Button 컴포넌트 수정:

Button 컴포넌트가 data-cy 속성을 받아 내부의 실제 HTML button 요소에 전달하도록 수정합니다.

// Button.tsx
interface ButtonProps {
  type: 'button' | 'submit' | 'reset';
  label: string;
  disabled: boolean;
  'data-cy'?: string; // data-cy 속성을 받을 수 있도록 추가
}

const Button: React.FC<ButtonProps> = ({ type, label, disabled, 'data-cy': dataCy }) => (
  <button type={type} disabled={disabled} data-cy={dataCy}>
    {label}
  </button>
);

export default Button;

UploadForm 컴포넌트에서 Button 컴포넌트 사용:

UploadForm 컴포넌트에서 Button 컴포넌트를 사용할 때 data-cy 속성을 전달합니다.
tsx
코드 복사

<Button
  data-cy='upload-btn'
  type='submit'
  label='꿀플레이스 로그 등록'
  disabled={!isValid}
/>
profile
기록은 기억이 된다

0개의 댓글