[Chapter 8]オーダーページとMock Service Workerの使用


作成するショッピングモールアプリケーションの紹介


オーダーページ


OK、完了ページ


このように3ページ作りましょう.

ショッピングモールアプリケーションの全体構造の作成


サーバは以下のようになります.

受注要約ページのフォームの作成



確認後、以下のように考えることができます.

やるべきこと


受注確認ボタンをクリックできるのは、「受注確認」チェック・ボックスをクリックすることのみです.

テストの作成→テストの実行→失敗


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();
})
予想通り失敗する.実際のコードの作成を開始します.

テストに対応する実際のコードの作成->テストの実行->合格


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にブロックし、シミュレーション応答を送信します.

MSW起動方式


ブラウザにサービスキットを登録して外部ネットワークリクエストを検出
要求が実際のサーバに到達すると、中間的にブロックされ、MSWクライアント側ライブラリに送信される.
登録されたプロセッサでリクエストを処理し、シミュレーション応答をブラウザに送信します.

使用方法


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

設定

npm install msw --save

ハンドラの作成


Handlerタイプ

  • 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を使用して商品画像のインポートをテストする



    製品セクションをテストします.
  • 型ファイル
  • を作成
  • Type.test.jsファイル
  • の作成
  • 製品ファイル
  • を生成する.
    今からテストを書きましょう.
    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が完了し、正しくロードされた場合は
    テストに合格した.

    サーバからデータをインポート中に発生したエラーの処理



    エラーが発生したときのエラー文の表現をします.
    2番目のテストケースを行います.

    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を作りましょう~!

    今このような姿が現れた.
    まず、私たちはまず全体的な枠組みを制定します.

    じゃ、このまま出てきます!