본문 바로가기
SPA/React

[React] Cypress Custom Command 사용하기

by J4J 2023. 5. 14.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 Cypress Custom Command 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Custom Command란?

 

Custom Command는 테스트 코드를 작성할 때 Cypress에서 제공해 주는 명령어가 아닌 사용자가 원하는 의도대로 동작될 수 있는 명령어를 자체적으로 구현한 것을 의미합니다.

 

Custom Command를 사용하는 대표적인 이유는 재사용성을 높이기 위함이라고 생각합니다.

 

테스트 코드를 작성하다 보면 중복되는 코드들이 수 없이 발생될 수 있습니다.

 

그럴 때마다 각 파일들에 중복 코드들을 매번 작성해주는 것은 비효율적이며 유지보수하기 어렵게 만들기 때문에 Custom Command를 생성한 뒤 단순 호출하는 방식을 선택하여 코드 개선을 할 수 있습니다.

 

 

반응형

 

 

JS 함수 사용

 

사실 테스트 코드가 아닌 일반 JS 코드들처럼 중복되는 소스를 하나의 함수로 만들어 재사용성을 동일하게 제공해 줄 수 있습니다.

 

예를 들어 상품을 등록하는 페이지에서 로그인을 위한 테스트 코드와, 자체적으로 커스텀한 textField의 input을 찾는 테스트 코드가 필요하다고 가정해 보겠습니다.

 

그리고 이들을 위한 소스는 다음과 같이 간단하게 구성해 봤습니다.

 

 

 

// /src/pages/login.tsx

import React from 'react';
import { useNavigate } from 'react-router';

const Login = () => {
    /**
     * navigate
     */
    const navigate = useNavigate();

    /**
     * handle
     */
    const handle = {
        login: () => {
            navigate('/home');
        },
    };

    return (
        <div>
            <input data-cy="id-input" type="text" placeholder="ID" />
            <input data-cy="password-input" type="password" placeholder="Password" />
            <button data-cy="login-button" onClick={handle.login}>
                로그인
            </button>
        </div>
    );
};

export default Login;

 

// /src/components/molecules/textField.tsx

import React from 'react';

interface Props {
    dataCy?: string;
}

const TextField = (props: Props) => {
    return (
        <div data-cy={props.dataCy}>
            <input type="text" />
        </div>
    );
};

export default TextField;

 

// /src/pages/register.tsx

import React, { useState } from 'react';
import TextField from '../components/molecules/textField';

const Register = () => {
    /**
     * useState
     */
    const [name, setName] = useState<string>('');
    const [price, setPrice] = useState<number>(0);

    return (
        <div>
            <input
                data-cy="name-input"
                type="text"
                placeholder="상품명을 입력해주세요."
                onChange={(e) => setName(e.target.value)}
            />

            <input
                data-cy="price-input"
                type="number"
                placeholder="가격을 입력해주세요."
                onChange={(e) => setPrice(Number(e.target.value))}
            />

            <TextField dataCy="textField-container" />
        </div>
    );
};

export default Register;

 

 

 

위와 같이 코드가 구성되어 있을 때 로그인과 textField의 input을 찾는 공통 코드는 다음과 같이 작성해 볼 수 있습니다.

 

// /cypress/support/commandFunction.ts

/**
 * cypress login command
 */
export const login = () => {
    // 페이지를 /login으로 이동
    cy.visit('/login');

    // id-input dom 요소에 '입력한 아이디'값을 입력
    cy.get('[data-cy=id-input]').type('입력한 아이디');
    // password-input dom 요소에 '1234'값을 입력
    cy.get('[data-cy=password-input]').type('1234');

    // login-button dom 요소가 존재하는지 확인 후 클릭 이벤트 발생
    cy.get('[data-cy=login-button]').should('exist').click();
};

/**
 * cypress get text field input dom
 */
export const getTextFieldInput = (dataCy: string) => {
    return cy.get(`[data-cy=${dataCy}] > input`);
};

 

 

 

이 상태에서 다음의 시나리오대로 테스트를 작성해 보겠습니다.

 

  • 로그인 수행
  • 상품 등록 페이지 이동
  • 상품명, 상품가격, 텍스트필드 값 입력

 

// /cypress/e2e/registerFunction.cy.ts

import { getTextFieldInput, login } from '../support/commandFunction';

describe('상품 등록 테스트', () => {
    before(() => {
        // 로그인
        login();
    });

    it('상품 정보를 입력하기', () => {
        // 페이지를 /register로 이동
        cy.visit('/register');

        // 이름 입력
        cy.get('[data-cy=name-input]').type('테스트 상품명');
        // 가격 입력
        cy.get('[data-cy=price-input]').type('2000');
        // 텍스트 필드 입력
        getTextFieldInput('textField-container').type('텍스트 필드');
    });
});

 

 

 

테스트 코드를 작성하고 테스트를 돌려보면 다음과 같은 결과를 확인해 볼 수 있습니다.

 

함수를 이용한 테스트 결과

 

 

 

 

Custom Command 사용

 

위와 동일한 결과가 나오는 테스트를 Custom Command를 이용한 코드로 변경해 보겠습니다.

 

 

 

먼저 commandFunction.ts 대신 다음 코드로 대체해 줍니다.

 

// /cypress/support/commands.ts

/// <reference types="cypress" />

/**
 * cypress login command
 */
Cypress.Commands.add('login', () => {
    // 페이지를 /login으로 이동
    cy.visit('/login');

    // id-input dom 요소에 '입력한 아이디'값을 입력
    cy.get('[data-cy=id-input]').type('입력한 아이디');
    // password-input dom 요소에 '1234'값을 입력
    cy.get('[data-cy=password-input]').type('1234');

    // login-button dom 요소가 존재하는지 확인 후 클릭 이벤트 발생
    cy.get('[data-cy=login-button]').should('exist').click();
});

/**
 * cypress get text field input dom
 */
Cypress.Commands.add('getTextFieldInput', (dataCy: string) => {
    cy.get(`[data-cy=${dataCy}] > input`);
});

 

 

 

그리고 타입 스크립트를 사용하시는 분들은 다음과 같이 선언한 명령어의 타입을 정의해줘야 합니다.

 

// cypress/support/cypress.d.ts

declare namespace Cypress {
    interface Chainable {
        login(): Chainable<JQuery<HTMLElement>>;
        getTextFieldInput(dataCy: string): Chainable<JQuery<HTMLElement>>;
    }
}

 

 

 

테스트 코드는 registerFunction.cy.ts대신 다음 코드로 대체해 줍니다.

 

여기서 주의할 점은 Custom Command가 정의된 파일을 import 해주는 것입니다.

(만약 /cypress/support/commands 파일에 정의했다면 import 해주지 않아도 됩니다.)

 

// /cypress/e2e/register.cy.ts

import '../support/commands';

describe('상품 등록 테스트', () => {
    before(() => {
        // 로그인
        cy.login();
    });

    it('상품 정보를 입력하기', () => {
        // 페이지를 /register로 이동
        cy.visit('/register');

        // 이름 입력
        cy.get('[data-cy=name-input]').type('테스트 상품명');
        // 가격 입력
        cy.get('[data-cy=price-input]').type('2000');
        // 텍스트 필드 입력
        cy.getTextFieldInput('textField-container').type('텍스트 필드');
    });
});

 

 

 

소스를 모두 변경하고 register.cy.ts에 대한 테스트를 돌려보면 다음과 같은 결과를 확인할 수 있습니다.

 

Custom Command 이용한 테스트 결과

 

 

 

 

결론 + 부가 정보

 

JS 함수를 사용할 때와 Custom Command를 사용하는 것을 비교해 보면 아직 사용 경험이 부족해서인지 모르겠지만 "어떤 것이 더 좋으니 이걸 사용해야겠다"라는 것을 느낄 수 없었습니다.

 

JS 함수를 사용하게 될 경우 테스트 코드가 아닌 소스들을 작성하는 것처럼 활용될 수 있다는 것을 확인할 수 있었고 반대로 Custom Command를 사용하게 될 경우 Cypress에서 제공해 주는 명령어처럼 활용될 수 있다는 것을 볼 수 있었습니다.

 

즉, 무엇이 더 좋다고 말할 수 없기에 같이 일하는 개발자분들이 어떤 코드 스타일을 더 선호하는지에 따라 사용 방법이 달라질 수 있을 것 같다고 느낄 수 있었습니다.

 

 

 

Cypress 공식 문서를 확인해 보면 Custom Command를 사용하는 방향은 두 가지가 있습니다.

 

  • Cypress.Commands.add() → 신규 명령어를 추가
  • Cypress.Commands.overwrite() → Cypress에서 제공해 주는 명령어를 재 정의

 

개인적인 생각으론 overwrite보단 add를 사용하는 것이 좋을 것 같다고 느껴집니다.

 

왜냐하면 add가 overwrite로 수행하려는 역할을 대체해 줄 수 있기도 하고 또한 Cypress에서 제공해주는 것처럼 동작하지 않으면 함께 개발할 때 혼란의 여지를 만들 수 있다고 생각하기 때문입니다.

 

 

 

마지막으로 공식 문서를 한번 더 확인해 보면 Custom Command의 쿼리는 /cypress/support/commands.js 파일에 정의되는 것을 추천하고 있습니다.

 

이유는 다음과 같습니다.

 

We recommend defining queries is in your cypress/support/commands.js file, since it is loaded before any test files are evaluated via an import statement in the supportFile.

 

 

 

파일 구조

 

 

 

 

 

 

 

 

이상으로 Cypress Custom Command 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글