주문페이지
확인, 완료 페이지
이렇게 세가지 페이지를 만들어 보자.
서버는 아래와같이 해준다.
이를 확인해보면 아래와 같이 생각할 수 있다.
주문 확인 체크 박스를 눌러야만 주문 확인 버튼을 누를 수 있다.
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이 뜬다. 이제 실제 코드를 작성해주자.
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를 안줘서 빨간 글자가 뜬다..! 이부분을 아래에서 해결해보자.
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를 추가해주면 에러가 사라진다!
첫 번째 페이지에서 Products OPtions들을 백엔드 서버에서 가져오는데 이러한 부분은 어떻게 테스팅 해 줄 수 있을까?
우리는, 서버에 요청을 보낼 때 그 요청을 가로채서 Mock Service Worker라는 것으로 요청을 처리하고 모의 응답을 보내주자(Mocked Response)
브라우저에 서비스 워커를 등록하여 외부로 나가는 네트워크 리퀘스트 감지
그 요청을 실제 서버로 갈 때 중간에 가로채서(intercept) MSW클라이언트 사이드 라이브러리로 보낸다.
그 후 등록된 핸들러에서 요청을 처리한 후 모의 응답을 브라우저로 보낸다.
npm install msw --save
핸들러 타입
- Rest
- Graphql
http,method,get,post,..
- req : 매칭 요청에 대한 정보
- res : 모의 응답 생성하는 기능적 유틸리티
- ctx : 모의 응답의 상태 코드, 헤더, 본문등을 설정하는데 도움되는 함수
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",
}
])
)
})
]
import { setUpServer } from 'msw/node';
import { handlers } from './handlers';
// create mocking server
export const server = setUpServer(...handlers);
import '@testing-library/jest-dom';
import {server} from './mocks/server';
beforeAll(()=> server.listen());
afterEach(()=> server.resetHandler());
afterAll(()=> server.close());
Products부분을 테스트해보자.
- Type 파일 생성
- Type.test.js 파일 생성
- 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부터 작성해주자.
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>
}
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
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("에러가 발생했습니다.");
})
import React from 'react'
export default function ErrorBanner({message}) {
return <div
data-testid="error-banner"
style={{backgroundColor : "red", color:"white"}}
>
{message}
</div>
}
에러배너를 작성해주고
if(error){
return <ErrorBanner message="에러가 발생했습니다." />
}
에서 에러처리를 해주면된다.
옵션정보를 가져와서 체크박스로 보여주는 부분을 테스트해보자!
물론 테스트를 작성하고 실패를 해봐야지~!
// 테스트 케이스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만들어보자 ~!
현재는 이러한 모습이 나오는데
우선, 전체적인 틀을 먼저 짜자
그러면 이렇게 나온다!