[Chapter 8] 주문 페이지 및 Mock Service Worker 사용

서희찬·2022년 4월 2일
0

The Origin : React

목록 보기
14/17
post-thumbnail

만들게 될 쇼핑몰 앱 소개

주문페이지

확인, 완료 페이지

이렇게 세가지 페이지를 만들어 보자.

쇼핑몰 앱을 위한 전체적인 구조 생성하기

서버는 아래와같이 해준다.

주문 요약 페이지를 위한 Form 생성


이를 확인해보면 아래와 같이 생각할 수 있다.

해야할 일

주문 확인 체크 박스를 눌러야만 주문 확인 버튼을 누를 수 있다.

테스트 작성 -> 테스트 실행 -> fail

SummaryPage.test.js

import SummaryPage from "../SummaryPage";
import {screen,render} from "@testing-library/react";

test('checkbox and button',()=>{
    render(<SummaryPage/>)
    const checkbox = screen.getByRole('checkbox',{
        name: "주문하려는 것을 확인하셨나요?"
    });
    expect(checkbox.checked).toEqual(false);

    const confirmButton = screen.getByRole('button',{
        name:"주문 확인"
    });
    
    expect(confirmButton.disabled).toBeTruthy();
})

예상대로 fail이 뜬다. 이제 실제 코드를 작성해주자.

테스트에 대응하는 실제 코드 작성-> 테스트 실행 -> Pass

SummaryPage.jsx

import React from 'react'

function SummaryPage() {
  return (
    <div>
        <form>
            <input
                type="checkbox"
                checked={false}
                id="confirm-checkbox"
            />
            {/* htmlFor과 id가 같아야함 */}
            {/* label과 test에 적은게 동일해야함 */}
            <label htmlFor="confirm-checkbox">
            주문하려는 것을 확인하셨나요? 
            </label>
            <button disabled={true} type="submit">
                주문 확인
            </button>
        </form>
    </div>
  )
}

export default SummaryPage

로 UI를 작성하고 나면
테스트가 성공적으로 패스한다.
아직, input에 onchange를 안줘서 빨간 글자가 뜬다..! 이부분을 아래에서 해결해보자.

input 태그에서 나오는 에러 해결하기

import React, { useState } from 'react'

function SummaryPage() {
    const [checked, setChecked] = useState(false);

  return (
    <div>
        <form>
            <input
                type="checkbox"
                checked={checked}
                onChange={(e)=>setChecked(e.target.checked)} //이벤트를 가져온다
                id="confirm-checkbox"
            />
            {/* htmlFor과 id가 같아야함 */}
            {/* label과 test에 적은게 동일해야함 */}
            <label htmlFor="confirm-checkbox">
            주문하려는 것을 확인하셨나요? 
            </label>
            <button disabled={!checked} type="submit">
                주문 확인
            </button>
        </form>
    </div>
  )
}

export default SummaryPage

이렇게 useState를 사용해서 하드코딩한 부분을 없애주고
onChange를 추가해주면 에러가 사라진다!

MSW(Mock Service Worker)에 대해서


첫 번째 페이지에서 Products OPtions들을 백엔드 서버에서 가져오는데 이러한 부분은 어떻게 테스팅 해 줄 수 있을까?
우리는, 서버에 요청을 보낼 때 그 요청을 가로채서 Mock Service Worker라는 것으로 요청을 처리하고 모의 응답을 보내주자(Mocked Response)

MSW 작동방식

브라우저에 서비스 워커를 등록하여 외부로 나가는 네트워크 리퀘스트 감지
그 요청을 실제 서버로 갈 때 중간에 가로채서(intercept) MSW클라이언트 사이드 라이브러리로 보낸다.
그 후 등록된 핸들러에서 요청을 처리한 후 모의 응답을 브라우저로 보낸다.

사용방법

https://mswjs.io/docs/getting-started/mocks/rest-api

설치

npm install msw --save

핸들러 생성

핸들러 타입

  • Rest
  • Graphql

http,method,get,post,..

  • req : 매칭 요청에 대한 정보
  • res : 모의 응답 생성하는 기능적 유틸리티
  • ctx : 모의 응답의 상태 코드, 헤더, 본문등을 설정하는데 도움되는 함수
handlers.js 작성
import {rest} from "msw";

export const handlers = [
    rest.get(`http://localhost:4000/products`,(req,res,ctx)=>{
        return res(
            ctx.json([
                {
                    name : "America",
                    imagePath:"/images/america.jpeg"
                },
                {
                    name : "England",
                    imagePath:"/images/england.jpeg"
                }
            ])
        )
    }),
    rest.get(`http://localhost:4000/options`,(req,res,ctx)=>{
        return res(
            ctx.json([
                {
                    name : "Insurance",
                },
                {
                    name : "Dinner",
                }
            ])
        )
    })
]

노드와 통합

서버 생성

server.js 작성
import { setUpServer } from 'msw/node';
import { handlers } from './handlers';

// create mocking server 
export const server = setUpServer(...handlers);

API Mocking 설정

setUpTest.js 작성
import '@testing-library/jest-dom';

import {server} from './mocks/server';

beforeAll(()=> server.listen());

afterEach(()=> server.resetHandler());

afterAll(()=> server.close());

MSW를 이용해서 상품 이미지 가져오는 테스트하기


Products부분을 테스트해보자.

  1. Type 파일 생성
  2. Type.test.js 파일 생성
  3. Products 파일 생성

이제 테스트를 작성해주자!

import { render, screen } from "@testing-library/react";
import Type from '../Type';


test("display product images from server",async()=>{
    render(<Type orderType="products" />);
    // 이미지 찾기
    const productImages = await screen.findAllByRole("img",{
        name: /product$/i // i를 통해 대소문자 구문없이 잡아줌 
    })
    expect(productImages).toHaveLength(2);

    const altText = productImages.map((element)=>element.alt);
    expect(altText).toEqual(["America product","England product"]);
})

당연히 아직까진 에러가 뜬다
이제 실제 코드를 작성해주자.

상품 정보를 가져오는 실제 코드 작성

type부터 작성해주자.

type.jsx

import React, { useState,useEffect } from 'react'
import axios from 'axios';
import Products from './Products';

export default function Type({orderType}) {
    const [items, setItems] = useState([]);

    useEffect(()=>{
        loadItems(orderType)
    },[orderType]);

    const loadItems = async(orderType)=>{
        try{
            let respons = await axios.get(`http://localhost:4000/${orderType}`)
            setItems(respons.data);
        }catch(error){

        }
    }
    const ItemComponent = orderType === "products" ? Products : null ;

    const optionItems = items.map((item)=>(
        //map에는 Key필수 
        <ItemComponent 
        key={item.name}
        name={item.name}
        imagePath={item.imagePath}
        />
    ))
  return <div>{optionItems}</div>
}

Products.jsx

import React from 'react'

export default function Products({
    name,imagePath
}) {
  return (
    <div>
        <img 
        style={{width:"75%"}} 
        src={`http://localhost:4000/${imagePath}`} 
        alt={`${name} product`}
        />
        <form style={{marginTop : "10px"}}>
            <label style={{textAlign:"right"}}>
                {name}
            </label>
            <input
            style={{marginLeft:7}}
            type="number"
            name="quantity"
            min="0"
            defaultValue={0}
            />
            

        </form>
    </div>
  )
}

작성하고 axios를 잘 불러오면
테스트가 패스 된다.

서버에서 데이터 가져올 때 발생하는 에러 처리하기


에러발생시 에러 문구 표출하는걸 만들어보자.
두번째 테스트케이스를 작성해주자

Type.test.jsx

// 테스트 케이스 2 
test("when fetching product data, face an error", async()=>{
    server.resetHandlers(
        rest.get(`http://localhost:4000/products`,(req,res,ctx)=>{
            return res(ctx.status(500))
        })
    )
    render(<Type orderType="products"/>)

    const errorBanner = await screen.findByTestId("error-banner")
    expect(errorBanner).toHaveTextContent("에러가 발생했습니다.");
})

에러 발생 시 작성하는 테스트 코드에 대한 실제 코드 작성하기

ErrorBanner.jsx
import React from 'react'

export default function ErrorBanner({message}) {
  return <div 
  data-testid="error-banner"
  style={{backgroundColor : "red", color:"white"}}
  >
    {message}

    </div>
}

에러배너를 작성해주고

Type.jsx
    if(error){
        return <ErrorBanner message="에러가 발생했습니다." />

    }

에서 에러처리를 해주면된다.

옵션 상품 정보 가져오기

옵션정보를 가져와서 체크박스로 보여주는 부분을 테스트해보자!
물론 테스트를 작성하고 실패를 해봐야지~!

Type.test.jsx
// 테스트 케이스3
test("fetch option imformation from server", async()=>{
    render(<Type orderType="options" />)

    // bring checkbox
    const optionCheckboxes = await screen.findAllByRole("checkbox");
    expect(optionCheckboxes).toHaveLength(2);
})

테스트를 작성해주었다.
이제 대응하는 실제코드를 작성해주자.
type.jsx에서 null대신 Options 컴포넌트를 가져오고
Options 컴포넌트를 아래와 같이 작성한다.
Options.jsx

import React from 'react'

export default function Options({name}) {
  return (
    <form>
        <input type="checkbox" id={`${name} option`}/>
        <label htmlFor={`${name} option`}>{name}</label>
    </form>
  )
}

상품 주문 페이지 UI 완성하기

이제 UI만들어보자 ~!

현재는 이러한 모습이 나오는데
우선, 전체적인 틀을 먼저 짜자

그러면 이렇게 나온다!

profile
부족한 실력을 엉덩이 힘으로 채워나가는 개발자 서희찬입니다 :)

0개의 댓글