회원가입

회원가입 기능

User Scenario

  • 이메일, 이름, 패스워드를 입력하여 회원가입을 한다.

  • 필드 값이 유효성에 어긋날시 에러 메시지가 노출된다.

  • 유효성에 어긋나거나 값이 채워지지 않은 경우 회원가입 버튼은 Disabled 상태가 된다.

  • 회원가입에 성공하면 회원가입 성공 페이지로 이동된다.

  • 회원가입 성공시 로그인 상태가 된다.

Unit Test

SignupForm.test.tsx

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Route } from "react-router-dom";

import VALID_MSG from "@/constants/validMsg";
import { withAllContexts, withRouter } from "@/tests/utils";

import SignupForm from "./SignupForm";

const context = describe;

describe("SignupForm", () => {
  function renderSignupForm() {
    return render(
      withAllContexts(withRouter(<Route path="/" element={<SignupForm />} />))
    );
  }

  it("render correctly", () => {
    renderSignupForm();

    expect(screen.getByRole("textbox", { name: "E-mail" })).toBeInTheDocument();
    expect(screen.getByRole("textbox", { name: "Name" })).toBeInTheDocument();
    expect(screen.getByLabelText("Password")).toBeInTheDocument();
    expect(screen.getByLabelText("Password Confirm")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "회원가입" }));
  });

  context("when email value is invalid", () => {
    it(`display error message "${VALID_MSG.EMAIL}"`, async () => {
      renderSignupForm();

      const emailInput = screen.getByRole("textbox", { name: "E-mail" });
      await userEvent.type(emailInput, "xxxxxxx");

      expect(emailInput).toHaveValue("xxxxxxx");
      expect(screen.getByText(VALID_MSG.EMAIL)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderSignupForm();

      const emailInput = screen.getByRole("textbox", { name: "E-mail" });
      await userEvent.type(emailInput, "xxxxxxx");

      expect(screen.getByRole("button", { name: "회원가입" })).toBeDisabled();
    });
  });

  context("when name value is invalid", () => {
    it(`display error message "${VALID_MSG.NAME}"`, async () => {
      renderSignupForm();

      const nameInput = screen.getByRole("textbox", { name: "Name" });
      await userEvent.type(nameInput, "xxxxxxx");

      expect(nameInput).toHaveValue("xxxxxxx");
      expect(screen.getByText(VALID_MSG.NAME)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderSignupForm();

      const nameInput = screen.getByRole("textbox", { name: "Name" });
      await userEvent.type(nameInput, "xxxxxxx");

      expect(screen.getByRole("button", { name: "회원가입" })).toBeDisabled();
    });
  });

  context("when password value is invalid", () => {
    it(`display error message "${VALID_MSG.PASSWORD}"`, async () => {
      renderSignupForm();

      const passwordInput = screen.getByLabelText("Password");
      await userEvent.type(passwordInput, "123123");

      expect(passwordInput).toHaveValue("123123");
      expect(screen.getByText(VALID_MSG.PASSWORD)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderSignupForm();

      const passwordInput = screen.getByLabelText("Password");
      await userEvent.type(passwordInput, "123123");

      expect(screen.getByRole("button", { name: "회원가입" })).toBeDisabled();
    });
  });

  context("when password confirm value is invalid", () => {
    it(`display error message "${VALID_MSG.PASSWORD_C}"`, async () => {
      renderSignupForm();
      await userEvent.type(screen.getByLabelText("Password"), "password");

      const passwordConfirmInput = screen.getByLabelText("Password Confirm");
      await userEvent.type(passwordConfirmInput, "pass");

      expect(passwordConfirmInput).toHaveValue("pass");
      expect(screen.getByText(VALID_MSG.PASSWORD_C)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderSignupForm();

      await userEvent.type(screen.getByLabelText("Password"), "password");
      await userEvent.type(screen.getByLabelText("Password Confirm"), "pass");

      expect(screen.getByRole("button", { name: "회원가입" })).toBeDisabled();
    });
  });

  context("when the form is empty", () => {
    it("submit button is disabled", async () => {
      renderSignupForm();

      expect(screen.getByRole("button", { name: "회원가입" })).toBeDisabled();
    });
  });
});

SignupFormStore.test.ts

import VALID_MSG from "@/constants/validMsg";

import SignupFormStore from "./SignupFormStore";

const context = describe;

describe("SignupFormStore", () => {
  let store: SignupFormStore;

  beforeEach(() => {
    store = new SignupFormStore();
  });

  describe("change email", () => {
    it("email is changed", () => {
      const email = "v_oyb@naver.com";
      store.changeEmail(email);

      expect(store.email).toBe(email);
    });

    context("when value is invalid", () => {
      it(`error is ${VALID_MSG.EMAIL}`, () => {
        store.changeEmail("invalid email");

        expect(store.error).toBe(VALID_MSG.EMAIL);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changeEmail("tester@example.com");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("change name", () => {
    it("name is changed", () => {
      const name = "정태웅";
      store.changeName(name);

      expect(store.name).toBe(name);
    });

    context("when value is invalid", () => {
      it(`error is ${VALID_MSG.NAME}`, () => {
        store.changeName("invalid name");

        expect(store.error).toBe(VALID_MSG.NAME);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changeName("정태웅");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("change password", () => {
    it("password is changed", () => {
      const password = "password";
      store.changePassword(password);

      expect(store.password).toBe(password);
    });

    context("when value is invalid", () => {
      it(`error is ${VALID_MSG.PASSWORD}`, () => {
        store.changePassword("123123");

        expect(store.error).toBe(VALID_MSG.PASSWORD);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changePassword("password");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("change password confirm", () => {
    it("password confirm value is changed", () => {
      const password = "password";
      store.changePasswordConfirm(password);

      expect(store.passwordConfirm).toBe(password);
    });

    context("when password entered doesn't match", () => {
      it(`error is ${VALID_MSG.PASSWORD_C}`, () => {
        store.changePassword("password");
        store.changePasswordConfirm("password1");

        expect(store.error).toBe(VALID_MSG.PASSWORD_C);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changePassword("password");
        store.changePasswordConfirm("password");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("with reset", () => {
    it("all field values are initialized", () => {
      store.changeEmail("tester@example.com");
      store.changeName("tester");
      store.changePassword("password");
      store.changePasswordConfirm("password");

      store.reset();

      expect(store.email).toBeFalsy();
      expect(store.name).toBeFalsy();
      expect(store.password).toBeFalsy();
      expect(store.passwordConfirm).toBeFalsy();
    });
  });
});
E2E Test
/// <reference types="cypress" />

// 이메일, 이름, 패스워드를 입력하여 회원가입을 한다.
// 필드 값이 유효성에 어긋날시 에러 메시지가 노출된다.
// 유효성에 어긋나거나 값이 채워지지 않은 경우 회원가입 버튼은 Disabled 상태가 된다.
// 회원가입에 성공하면 회원가입 성공 페이지로 이동된다.
// 회원가입에 성공시 로그인 상태가 된다.

describe("Signup", () => {
  beforeEach(() => {
    cy.backdoor();
    cy.visit("/signup");
  });

  context("when form values are invalid", () => {
    it("unable to signup", () => {
      cy.findByRole("textbox", { name: "E-mail" }).type("invalid email");
      cy.findByText("이메일 형식이 올바르지 않습니다.").should("exist");

      cy.findByRole("textbox", { name: "Name" }).type("invalid name");
      cy.findByText("이름 형식이 올바르지 않습니다.").should("exist");

      cy.findByLabelText("Password").type("invalid password1@");
      cy.findByText("비밀번호 형식이 올바르지 않습니다.").should("exist");

      cy.findByLabelText("Password Confirm").type("invalid password");
      cy.findByText("비밀번호가 일치하지 않습니다.").should("exist");

      cy.findByRole("button", { name: "회원가입" }).should("be.disabled");
    });
  });

  context("when signup succeed ", () => {
    beforeEach(() => {
      cy.findByRole("textbox", { name: "E-mail" }).type("tester2@example.com");
      cy.findByRole("textbox", { name: "Name" }).type("테스터");
      cy.findByLabelText("Password").type("password");
      cy.findByLabelText("Password Confirm").type("password");
      cy.findByRole("button", { name: "회원가입" }).click();
    });
    it("redirected signup complete page", () => {
      cy.findByText(/Signup Complete/).should("exist");
      cy.url().should("include", "/signupComplete");
    });

    it("will be logged in status", () => {
      cy.findByRole("link", { name: "Login" }).should("not.exist");
      cy.findByRole("link", { name: "Cart" }).should("exist");
      cy.findByRole("button", { name: "Logout" }).should("exist");
    });
  });
});

Last updated