Stephen Scott๋์ด ์ฐ์ The Right Way to Test React Components๋ผ๋ ๊ธ์ ๋ฒ์ญ์ ๋๋ค. ๋ณธ ๋ฒ์ญ ๊ธ์ ์ ์์์ ํ๊ฐํ์ ์์ฑ๋์ด ์์ต๋๋ค.
๋ฒ์ญ๋ฌธ์ด์ง๋ง ํ๊ตญ์ด๋ก ์ฝํ๊ธฐ ์ฝ๊ฒ ํ๊ฒ ์ํด ์์์์ ์๋๊ฐ ๋ณํ์ง ์๋ ์ ์์ ์ ๊ทน์ ์ผ๋ก ์์ญ์ ํ๊ณ ์์ต๋๋ค. ๋ฐ๋๋ก ๋ณธ๋ฌธ์์์ ์ค์ํ๊ฒ ๋ค๋ค์ง๋ ๊ฐ๋ ๋ค(React์ Component, Prop, State์ ๊ฐ์ด)์ ๋ฒ์ญ์ ์คํด๊ฐ ์๊ธธ ์ ์์ผ๋ฏ๋ก ์๋ฌธ์ ๊ทธ๋๋ก ์๋๋ค.
๋ฒ์ญ์ : Junyoung Choi (Rokt33r)
์ง๊ธ React Component๋ฅผ ํ ์คํธํ๊ธฐ ์ํ "์ฌ๋ฐ๋ฅธ" ๋ฐฉ๋ฒ์ ๋ํด ๋ง์ ํผ๋์ด ์์ต๋๋ค. ๋น์ ์ ๋ชจ๋ ํ ์คํธ๋ฅผ ์์์ ์ผ๋ก ์จ์ผํ ๊น์? ์๋๋ฉด ์ค๋ ์ท๋ง ํ์ฉํด์ผ ํ ๊น์? ์ด์ฉ๋ฉด ๋ ๋ค? Prop๋ค๋ ํ ์คํธ ํ ๊ฑด๊ฐ์? State๋? ์คํ์ผ์ด๋ ๋ ์ด์์์ ์ด๋ป๊ฒ ํ ๊ฑด๊ฐ์?
์ ์๊ฐ์๋ ํ๋์ "์ฌ๋ฐ๋ฅธ" ๋ฐฉ๋ฒ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค๋ง, ๊ทธ๋๋ ํจ๊ณผ๊ฐ ์๋ ๋ช๊ฐ์ง ํ๊ณผ ํจํด๋ค์ ์๊ฐ๋๋ฆฌ๊ณ ์ถ์ต๋๋ค.
์ค๋งํธํฐ์ ์ ๊นํ๋ฉด๊ณผ ๊ฐ์ด ๋์ํ๋ LockScreen
Component๋ฅผ ํ
์คํธํ๊ณ ์ถ๋ค๊ณ ๊ฐ์ ํฉ์๋ค. ์ด ์ฑ์ ๋ค์๊ณผ ๊ฐ์ ๊ฒ์ ํฉ๋๋ค:
- ํ์ฌ ์๊ฐ์ ํ์ํ๋ค.
- ์ ์ ๊ฐ ์ค์ ํ ๋ฉ์ธ์ง๋ฅผ ํ์ํ ์ ์์ด์ผ ํ๋ค.
- ์ ์ ๊ฐ ์ค์ ํ ๋ฐฐ๊ฒฝ ํ๋ฉด์ ํ์ํ ์ ์์ด์ผ ํ๋ค.
- ๋ฐ์ด์ ์ ๊ธ ํด์ ์์ ฏ์ ๋ฐ์ ๋ฌ์ผํ๋ค.
์๋ง ์ด๋ฐ ๋ชจ์์ด ๋ ๊ฒ์ ๋๋ค:
์ฌ๊ธฐ์์ ์ง์ ์จ๋ณด์ค ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋๋ GitHub์์ ์ฐพ์ผ์ค ์ ์์ต๋๋ค.
์ต์์ ๋ ๋ฒจ์ App
Component๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
App.jsx
import React from "react";
import LockScreen from "./LockScreen";
export default class App extends React.Component {
render() {
return (
<LockScreen
wallpaperPath="react_wallpaper.png"
userInfoMessage="์ด๊ฑด Tim์ ์ ํ์
๋๋ค. ๋ง์ฝ ์ฐพ์ผ์๋ฉด ๊ทธ์๊ฒ ๋๋ ค์ฃผ์ธ์. ์ด๊ฒ ์์ผ๋ฉด ์ฌํผํ๊ณ ์์๊ฑฐ์์."
onUnlocked={() => alert("์ด๋ฆผ!")}
/>
);
}
}
๋ณด๋ค์ถ์ด, LockScreen
์ ์ธ๊ฐ์ง Prop๋ค์ ๋ฐ์ต๋๋ค: wallpaperPath
, userInfoMessage
, ๊ทธ๋ฆฌ๊ณ onUnlocked
.
LockScreen
์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
LockScreen.jsx
import React, { PropTypes } from "react";
import ClockDisplay from "./ClockDisplay";
import TopOverlay from "./TopOverlay";
import SlideToUnlock from "./SlideToUnlock";
export default class LockScreen extends React.Component {
static propTypes = {
wallpaperPath: PropTypes.string,
userInfoMessage: PropTypes.string,
onUnlocked: PropTypes.func,
};
render() {
const {
wallpaperPath,
userInfoMessage,
onUnlocked,
} = this.props;
return (
<div
style={{
height: "100%",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
backgroundImage: wallpaperPath ? `url(${wallpaperPath})` : "",
backgroundColor: "black",
backgroundPosition: "center",
backgroundSize: "cover",
}}
>
<ClockDisplay />
{userInfoMessage ? (
<TopOverlay
style={{
padding: "2em",
marginBottom: "auto",
}}
>
{userInfoMessage}
</TopOverlay>
) : null}
<SlideToUnlock onSlide={onUnlocked} />
</div>
);
}
}
LockScreen
๋ ๋ค๋ฅธ Component๋ค์ ๋ถ๋ฅด๊ณ ์์ง๋ง, ์ฐ๋ฆฌ๋ LockScreen
๋ง ํ
์คํธ๋ฅผ ํ ๊ฒ์ด๋ฏ๋ก, ์ง๊ธ์ ์ฌ๊ธฐ์๋ง ์ง์คํฉ์๋ค.
LockScreen
๋ฅผ ํ
์คํธ ํ๋ ค๋ฉด, ์ฐ์ ๊ทธ๊ฒ์ Contract๊ฐ ๋ญ์ธ์ง ์์์ผ ํฉ๋๋ค. ํ Component์ Contract๋ฅผ ์ดํดํ๋ ๊ฒ์ React Component์ ํ
์คํธ์์ ๋งค์ฐ ์ค์ํ ๋ถ๋ถ์
๋๋ค. Contract๋ Component๋ก๋ถํฐ ๊ธฐ๋๋๋ ๋์๊ณผ ์ฌ์ฉ์ ์ด๋คํ ๊ฐ์ ์ด ์ด์น์ ๋ง๋ ์ง๋ฅผ ์ ์ํฉ๋๋ค. ๋ถ๋ช
ํ Contract๊ฐ ์๋ค๋ฉด, ๋น์ ์ Component๋ ์๋ง ์ฝ๊ฒ ์ดํดํ๊ธฐ ์ด๋ ค์ธ ๊ฒ์
๋๋ค. ํ
์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ Component์ Contract๋ฅผ ์ ๋๋ก ์ ์ํ๊ธฐ ์ํ ์์ฃผ ์ข์ ๋ฐฉ๋ฒ์
๋๋ค.
์ญ์ ์ฃผ: Contract๋ ๋น์ฆ๋์ค์์์ ๊ณ์ฝ์ ๋ํ ์์ ๋ก์จ ๋ณธ๋ฌธ์์ Component๊ฐ ์ด๋ป๊ฒ ํ์๋๊ณ ๋ฌด์์ ํด์ผํ๋์ง ๋ฑ์ ๊ฒฐ์ ํ๋ ์ฌ์(Specification) ๊ณผ ๊ฐ์ ์๋ฏธ๋ก ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
๊ฐ๊ฐ์ React Component๋ Contract๋ฅผ ์ ์ํ๋ ๊ธฐ๋ฅ์ ์ ์ด๋ ํ๋๋ ๊ฐ์ง๊ณ ์์ต๋๋ค.
- ๋ฌด์์ ๋ ๋์ํค๋๊ฐ (์ด์ฉ๋ฉด ์๋ฌด๊ฒ๋ ์ ํ ์๋ ์๊ฒ์ฃ )
๊ทธ๋ฆฌ๊ณ , ๋๋ถ๋ถ์ Component Contract๋ ์ฃผ๋ก ๋ค์ ์์๋ค์ ์ํฅ์ ๋ฐ์ต๋๋ค:
- Component๊ฐ ๋ฐ์ props
- Component๊ฐ ๊ฐ์ง๋ state
- ์ ์ ์ ์ํธ์์ฉ์ด ์ผ์ด๋ ๋ Component๊ฐ ํ๋ ์ผ (ํด๋ฆญ, ๋๋๊ทธ, ํค๋ณด๋์ ๋ ฅ ๋ฑ)
Component Contract์ ๋ ์ํฅ์ ๋ผ์น๋ ๊ฒ๋ค์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- Component๊ฐ ํ์๋๊ณ ์๋ context
- Component์ ์ธ์คํด์ค ๋ฉ์๋๊ฐ ๋ถ๋ฌ์์ก์ ๋, ์ปดํฌ๋ํธ๊ฐ ํ๋ ๊ฒ (public ref interface)
- Component ์ผ๋ถ lifecycle์์ ์ผ์ด๋๋ Side effects (componentDidMount, componentWillUnmount, ๋ฑ)
์ฌ๋ฌ๋ถ์ Component์ Contract๋ฅผ ์๊ธฐ ์ํด์ ๋ค์์ ์ง๋ฌธ์ ๋ํด ์ค์ค๋ก ๋ตํด๋ณด์ ์ผ ํฉ๋๋ค:
- Component๋ ๋ฌด์์ ๋ ๋๋งํ๋๊ฐ?
- Component๋ ๋ค์ํ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ฒ๋ค์ ๋ ๋๋ง ํ๋๊ฐ?
- Prop์ผ๋ก ๋๊ฒจ์ค ํจ์๋ฅผ Component๋ ๋ฌด์ผ ์ํด ์ฐ๋๊ฐ? ๊ทธ๋๋ก ์ฌ์ฉํ๋๊ฐ? ์๋๋ฉด ๋ ๋ค๋ฅธ Component๋ก ๋๊ฒจ์ค ๊ฑด๊ฐ? ๋ง์ฝ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค๋ฉด, ๋์์ ๋ฌด์์ ํ๋๊ฐ?
- ์ ์ ๊ฐ Component์ ์ํธ์์ฉ์ ํ๋ฉด ๋ฌด์จ ์ผ์ด ์ผ์ด๋๋๊ฐ?
LockScreen
์ render
๋ฉ์๋๋ฅผ ์ดํด๋ณด๊ณ , ์ด๋์์ ๋ ๋๋ง์ด ๋ฐ๋ ์ ์๋์ง ์ฝ๋ฉํธ๋ก ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค. 3ํญ ์ฐ์ฐ์, If๋ฌธ, Switch๋ฌธ์ด ๋จ์๊ฐ ๋ ๊ฒ์
๋๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ์ข ๋ ์ฝ๊ฒ Contract๋ก ์ธํ ๋ณํ๋ฅผ ํ์
ํ ์ ์์ต๋๋ค.
LockScreen-render.jsx
render() {
const {
wallpaperPath,
userInfoMessage,
onUnlocked,
} = this.props;
return (
<div
style={{
height: "100%",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
// ๋ง์ฝ wallpaperPath props์ด ๋๊ฒจ์ง๋ฉด, div์ CSS background-image๋ก ๋ค์ด๊ฐ ๊ฒ์ด๋ค.
// ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด, ๋น ๋ฌธ์์ด์ด ๋์ด์ผํ๋ค.(= ์๋ฌด๋ฐ ์คํ์ผ๋ ๋ฃ์ง ์๋๋ค.)
backgroundImage: wallpaperPath ? `url(${wallpaperPath})` : "",
backgroundColor: "black",
backgroundPosition: "center",
backgroundSize: "cover",
}}
>
<ClockDisplay />
{/*
๋ง์ฝ userInfoMessage prop์ด ๋๊ฒจ์ง๋ฉด, TopOverlay๋ฅผ ํตํด userInfoMessage๋ฅผ ํ์์ํจ๋ค.
๋๊ฒจ์ง์ง ์๋๋ค๋ฉด, ์ฌ๊ธฐ์ ์๋ฌด๊ฒ๋ ๋ ๋๋ง ์ํค์ง ์๋๋ค.(null)
*/}
{userInfoMessage ? (
<TopOverlay
style={{
padding: "2em",
marginBottom: "auto",
}}
>
{userInfoMessage}
</TopOverlay>
) : null}
<SlideToUnlock onSlide={onUnlocked} />
</div>
);
}
์ด์ LockScreen
์ contract๋ฅผ ๋ํ๋ด๋ 3๊ฐ์ง์ Contraint์ ๋ํด ์๊ฒ ๋์์ต๋๋ค:
- ๋ง์ฝ
wallpaperPath
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ฐ์ฅ ๋ฐ๊นฅ์div
๋ ๊ทธ ๊ฐ์ด ๋ฌด์์ด๋ url(...)๋ก ๊ฐ์ธ์ฌ์ ธ์background-image
CSS property๋ฅผ inline ์คํ์ผ๋ก ๊ฐ์ง๊ฒ ๋ฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๋ช๊ฐ์ง inline ์คํ์ผ๊ณผ ํจ๊ปTopOverlay
์ ์์์ผ๋ก ๋๊ฒจ์ค๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
์ญ์ ์ฃผ: Constraint๋ Contract(์ฌ์)์ ๋ํ ๊ฐ๊ฐ์ ํญ๋ชฉ, ๋ฐ๊ฟ๋งํ๋ฉด ์ธ์ธํ ๊ท์น์ ์๋ฏธํฉ๋๋ค. ์๋์ ์ผ๋ก Contract์ Constraint๋ฅผ ํผ๋ํ์ง ์๋๋ก ๋ ๋จ์ด๋ ์์ด ๊ทธ๋๋ก ์ป์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ Contract์์ ๋ช๊ฐ์ง Contraint์ ํญ์ ๊ทธ๋๋ก๋ผ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค:
div
๋ ๋ฌด์์ด ๋ค์ด์ค๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค. inline ์คํ์ผ์ ๊ฐ์ง๋๋ค.ClockDisplay
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค. ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค.SlideToUnlock
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค.onUnlocked
์ด ์ ์๋๋ ๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋ก ๋๊ฒจ์ค๋๋ค.
Component์ propTypes
์ญ์ Contract๋ฅผ ์๊ธฐ ์ํ ๋จ์๋ฅผ ์ฐพ๊ธฐ ์ข์ ๊ณณ์
๋๋ค. ์ฌ๊ธฐ์ ์ข ๋ ๋ง์ Contraint๋ค์ ์๊ฒ ๋์์ต๋๋ค:
wallpaperPath
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.userInfoMessage
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.onUnlocked
๋ ํจ์์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.
์ด๋ฐ์์ผ๋ก Component์ Contract๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์๋ง ๋ ๋ง์ Contraint์ด ์์ด์ผ ํ ๊ฒ์ด๊ณ , Production ์์ค์ ์ฝ๋์ด๋ฉด (ํ์ง์ ๋ณด์ฆํ๊ธฐ ์ํด) ๊ฐ๋ฅํ ํ ๋ ๋ง์ด ์ฐพ๊ณ ์ถ์ผ์ค ๊ฒ๋๋ค. ์ด๊ฒ์ ์์ ์ด๋ฏ๋ก ์ผ๋จ ์ด๊ฒ๋ค๋ง์ ๊ฐ์ง๊ณ ํด๋ด ์๋ค. ์ฌ๋ฌ๋ถ๋ค์ ์ธ์ ๋ ์ง Contraint๋ค์ ๋ ๋ฐ๊ฒฌํ๋ฉด ํ ์คํธ๋ฅผ ๋๋ฆด ์ ์์ต๋๋ค.
wallpaperPath
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.userInfoMessage
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.onUnlocked
๋ ํจ์์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.div
๋ ํญ์ ๋ ๋๋ง๋๋ฉฐ, ๋ชจ๋ ๊ฒ์ ๋ด๊ณ ์์ต๋๋ค. inline ์คํ์ผ์ ๊ฐ์ง๋๋ค.ClockDisplay
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค. ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค.SlideToUnlock
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค.onUnlocked
์ด ์ ์๋๋ ๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋ก ๋๊ฒจ์ค๋๋ค.- ๋ง์ฝ
wallpaperPath
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ฐ์ฅ ๋ฐ๊นฅ์div
๋ ๊ทธ ๊ฐ์ด ๋ฌด์์ด๋url(...)
๋ก ๊ฐ์ธ์ฌ์ ธ์background-image
CSS property๋ฅผ inline ์คํ์ผ๋ก ๊ฐ์ง๊ฒ ๋ฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๋ช๊ฐ์ง inline ์คํ์ผ๊ณผ ํจ๊ปTopOverlay
์ ์์์ผ๋ก ๋๊ฒจ์ค๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
์ผ๋ถ Contraint์ ํ ์คํธํ ๊ฐ์น๊ฐ ์์ง๋ง, ๊ทธ๋ ์ง ์์ ๊ฒ๋ ์์ต๋๋ค. ์ ๋ ํ ์คํธํ ๊ฐ์น๊ฐ ์๋ ๊ฒ๋ค์ 3๊ฐ์ง Rules of thumb์ ํตํด ๊ฒฐ์ ํฉ๋๋ค:
์ญ์ ์ฃผ: Rules of Thumb๋ ๊ฒฝํ์์ผ๋ก ์ป์ด์ง ๋ง๋ ๊ท์น๋ค์ ์๋ฏธํฉ๋๋ค. ์ฌ๊ธฐ์ ์์์์ ๊ฒฝํ์ ์ธ๋งํ ๊ท์น์ผ๋ก, ์ดํ๋ ์ด Rules of Thumb๋ฅผ ํตํด ํ ์คํธํ Constraint๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. ์๋ฏธ์ Contract, Constraint ๊ทธ๋ฆฌ๊ณ Rules of Thumb ๋ชจ๋ ๊ท์น์ผ๋ก ๋ฒ์ญ๋ ์ฐ๋ ค๊ฐ ์์ผ๋ฏ๋ก ์ด ์ญ์ ์๋ฌธ ๊ทธ๋๋ก ์๋๋ค.
- ํ ์คํธ ์ฝ๋์ ์คํ ์ฝ๋๊ฐ ๊ทธ๋๋ก ์ค๋ณต๋์ด ์ฐ์ฌ์ ธ ์๋๊ฐ? ์ด๊ฒ์ ํ ์คํธ๊ฐ ๋ง๊ฐ์ง๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
- ํ ์คํธ ์ฝ๋์ Assertion์ด ์ด๋ฏธ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋๊ฐ ๋ณด์ฆํ๋ (๊ทธ๋ฆฌ๊ณ ์ฑ ์์ง๋) ๋ถ๋ถ๊ณผ ์ค๋ณต๋๋๊ฐ? (๊ฐ์ฅ ์ค์)
- Component ๋ฐ๊นฅ์์์ ์์ ์์, ์ด๋ฌํ ์ธ๋ถํญ๋ชฉ๋ค์ด ์ค์ํ๊ฐ? ํน์ ๊ทธ์ ๋ด๋ถ์ ์ธ ์ฌ์ ์ธ๊ฐ? ์ด๋ฌํ ๋ด๋ถ์ ์ธ ์ฌ์ ์ผ๋ก ์ธํ ์ํฅ๋ Component์ public API๋ฅผ ์ฐ๋ ๊ฒ๋ง์ผ๋ก ์ค๋ช ๋ ์ ์๋๊ฐ?
์ด๊ฒ๋ค์ ๊ทธ์ Rules of thumb์ด๋ฏ๋ก, ์ด๋ ต๋ค๋ค๊ณ ํ ์คํธํ๊ธฐ ์ซ๋ค๋ ๋ณ๋ช ์ผ๋ก ์ฐ์ฌ์ง์ง ์๋๋ก ์กฐ์ฌํ์ธ์. ์ข ์ข , ํ ์คํธํ๊ธฐ ์ด๋ ค์ ๋ณด์ด๋ ๊ฒ๋ค์ด ํ ์คํธ์์ ๊ฐ์ฅ ์ค์ํฉ๋๋ค. ํ ์คํธ๊ฐ ํํด์ง๋ ์ฝ๋๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋๋จธ์ง ์ฝ๋๊น์ง๋ ์ฌ๋ฌ ๋ง์ ๊ฐ์ ๋ค์ ์ก์ ์ฃผ๊ธฐ ๋๋ฌธ์ ์์ ์ ์ธ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋ค ์ ์์ต๋๋ค.
๊ทธ๋ผ Constraint๋ฅผ ์ดํด๋ณด๊ณ ๋ฌด์์ ํ ์คํธํ ์ง ๋ง์ง๋ฅผ Rules of Thumb๋ฅผ ํตํด ๊ฒฐ์ ํด๋ด ์๋ค. ๋จผ์ ์ฒ์ 3๊ฐ๋ถํฐ ์์ํฉ์๋ค:
wallpaperPath
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.userInfoMessage
๋ ๋ฌธ์์ด์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.onUnlocked
๋ ํจ์์ด๋ฉฐ, ์ ํ์ ์ผ๋ก ์ฃผ์ด์ง ์ ์์ต๋๋ค.
์ด Constraint๋ค์ React์ PropTypes
๋ฉ์ปค๋์ฆ์ด ์ ๊ฒฝ์ฐ๋ ๋ถ๋ถ์
๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก, ์ด๊ฑธ ํ
์คํธ๋ก ๋ง๋๋๊ฑด Rule #2(์ด๋ฏธ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ณด์ฆํ๊ณ ์์)๋ฅผ ์๋ฐฐํ๊ฒ ๋ฉ๋๋ค. ๊ณ ๋ก, ์ ๋ Prop์ ํ์
๋ค์ ํ
์คํธ ํ์ง ์์ต๋๋ค. ์ ๋, ์ข
์ข
ํ
์คํธ๋ค์ ๋ฌธ์๋ฅผ ๊ฒธํ ์ ์๊ธฐ์, ๊ฐ๋ Rule #2๋ฅผ ์๋ฐฐํ๋๋ผ๋ ์คํ์ฝ๋๊ฐ ์ด๋ค ํ์
์ ๋ฐ์์ง ์๊ธฐ ์ด๋ ต๋ค๋ฉด ํ
์คํธ๋ฅผ ์ฐ๊ธฐ๋ ํฉ๋๋ค. ํ์ง๋ง, propTypes
๋ ์ถฉ๋ถํ ์ข๊ณ ์ฝ๊ธฐ๋ ์ฌ์ฐ๋ฏ๋ก ํ์ง ์์ต๋๋ค.
๋ค์ Constraint๋ฅผ ๋ด ์๋ค:
div
๋ ํญ์ ๋ ๋๋ง๋๋ฉฐ, ๋ชจ๋ ๊ฒ์ ๋ด๊ณ ์์ต๋๋ค. inline ์คํ์ผ์ ๊ฐ์ง๋๋ค.
์ด๊ฑด 3๊ฐ์ง์ Constraint๋ค๋ก ๋๋์ด ์ง ์ ์์ต๋๋ค.
div
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.div
๋ ๋ ๋๋ง๋๋ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ๋ค์ ๋ด๊ณ ์์ต๋๋ค.div
๋ inline ์คํ์ผ์ ๊ฐ์ง๋๋ค.
์ฒ์ 2๊ฐ์ Constraint๋ค์ Rules of Thumb๋ฅผ ์๋ฐํ์ง ์์ผ๋ฏ๋ก, ์ฐ๋ฆฌ๋ ์ด๊ฒ๋ค์ ํ ์คํธํ ๊ฒ์ ๋๋ค. ํ์ง๋ง, 3๋ฒ์งธ๋ฅผ ๋ด ์๋ค.
๋ค๋ฅธ constraint๊ฐ ๋ค๋ฃจ๊ณ ์๋ background-image
๋ฅผ ์ ์ธํ๊ณ , div
๋ ๋ค์์ ์คํ์ผ๋ค์ ๊ฐ์ง๋๋ค:
height: "100%",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
backgroundColor: "black",
backgroundPosition: "center",
backgroundSize: "cover",
๋ง์ฝ ์ด ์คํ์ผ๋ค์ด ์๋์ง ํ ์คํธ๋ฅผ ํ๋ ค ํ๋ค๋ฉด, ์ ํจํ Assertion๋ฅผ ์ํด ๊ฐ ์คํ์ผ์ ๊ฐ์ ์๋ ๊ทธ๋๋ก ํ ์คํธํด์ผ ํ ๊ฒ์ ๋๋ค. ๊ณ ๋ก Assertion๋ค์ ๋ค์๊ณผ ๊ฐ์ด ๋ฉ๋๋ค:
div
๋height
์คํ์ผ์100%
์ ๊ฐ์ ๊ฐ์ง๋ค.div
๋display
์คํ์ผ์flex
๋ก ๊ฐ์ง๋ค.- โฆ๋ค๋ฅธ ์คํ์ผ๋ค๋ ๋๊ฐ์ด ํ๋ค.
๊ฐ๊ฒฐํ ํ
์คํธ๋ฅผ ์ํด toMatchObject
๊ฐ์ ๊ฑธ ์ฐ๊ณ ์๋๋ผ๋, ์ด๊ฑด ์คํ ์ฝ๋์ ์คํ์ผ๊ณผ ์ค๋ณต๋๋๋ฐ๋ค ๋ง๊ฐ์ง๊ธฐ ์ฝ๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ ๋ค๋ฅธ ์คํ์ผ์ ์ถ๊ฐํ๋คํด๋, ๋๊ฐ์ ์ฝ๋๋ฅผ ํ
์คํธ์๋ ๋ฃ์ด์ผ ํ ๊ฒ๋๋ค. ๋ํ, Component์ ํ๋์ด ๋ฐ๋์ง ์๋๋ผ๋, ์คํ์ผ์ ์ฝ๊ฐ ์์ ํ๊ฒ๋๋ฉด ํ
์คํธ์์์ ์คํ์ผ ์ญ์ ๊ทธ๋๋ก ์์ ๋์ด์ ธ์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ด Constraint๋ Rule #1๋ฅผ ์๋ฐํ๊ณ ์์ต๋๋ค.(์คํ ์ฝ๋์ ์ค๋ณต๋จ; ๋ง๊ฐ์ง๊ธฐ ์ฌ์) ์ด๋ฐ ์ด์ ๋ก, ์ ๋ ๋ฐํ์์์ ๋ณํ๊ฐ ์ผ์ด๋์ง ์๋ ํ inline ์คํ์ผ ํ
์คํธ๋ฅผ ํ์ง ์์ต๋๋ค.
์ข ์ข "์ด๊ฒ์ ์ด๊ฒ์ด ํ๋ ๊ฑธ ํ๋ค"๋ "์ด๊ฒ์ ์คํ ์ฝ๋์์ ํ๋ ๊ฑธ ๊ทธ๋๋ก ํ๋ค" ๊ฐ์ ์ฝ๋๋ฅผ ์ฐ์ ๋ค๋ฉด, ์ด๋ฌํ ํ ์คํธ๋ค์ ๋ถํ์ ํ๊ฑฐ๋ ๋๋ฌด ๋ช ๋ฐฑํ ๊ฒ๋๋ค.
์ด์ ๊ทธ ๋ค์ 2๊ฐ์ Constraint๋ฅผ ๋ด ์๋ค:
ClockDisplay
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค. ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค.SlideToUnlock
๋ ํญ์ ๋ ๋๋ง๋ฉ๋๋ค.onUnlocked
์ด ์ ์๋๋ ๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋ก ๋๊ฒจ์ค๋๋ค.
์ด๊ฒ๋ค์ ๋ค์๊ณผ ๊ฐ์ด ๋๋ ์ ์์ต๋๋ค:
ClockDisplay
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.- ๋ ๋๋ง๋
ClockDisplay
๋ ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค. SlideToUnlock
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.onUnlocked
Prop์ด ์ ์๋์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋กonUnlocked
๋ฅผ ๋ฐ์ต๋๋ค.onUnlocked
Prop์ด ์ ์๋์ง ์์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop ์ญ์undefined
๋ฅผ ๋ฐ์ต๋๋ค.
์ด๋ฌํ Constraint๋ค์ 2๊ฐ์ง ์นดํ ๊ณ ๋ฆฌ๋ก ์ ๋ฆฌ๋ฉ๋๋ค: "์ด๋ค Component๊ฐ ๋ ๋๋ง๋๋ค", ๊ทธ๋ฆฌ๊ณ "๋ ๋๋ง๋ Component๋ ์ด๋ฌํ Prop์ ๋ฐ๋๋ค". ์ด๊ฒ๋ค์ ์ด๋ป๊ฒ ์ฌ๋ฌ๋ถ์ Component๊ฐ ๋ค๋ฅธ Component๋ค๊ณผ ์ํธ์์ฉ์ ํ๋ ์ง๋ฅผ ์ค๋ช ํ๊ธฐ ๋๋ฌธ์, ๋๊ฐ์ง ๋ชจ๋ ํ ์คํธ๊ฐ ํ์ํ ๋งํผ ์ค์ํฉ๋๋ค. ๊ณ ๋ก, ์ฐ๋ฆฌ๋ ์ด๋ฌํ Constraint๋ค์ ๋ชจ๋ ํ ์คํธ ํ ๊ฒ๋๋ค.
๋ค์ Constraint๋ฅผ ๋ด ์๋ค:
- ๋ง์ฝ
wallpaperPath
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ฐ์ฅ ๋ฐ๊นฅ์div
๋ ๊ทธ ๊ฐ์ด ๋ฌด์์ด๋ url(...)๋ก ๊ฐ์ธ์ฌ์ ธ์background-image
CSS property๋ฅผ inline ์คํ์ผ๋ก ๊ฐ์ง๊ฒ ๋ฉ๋๋ค.
์ด์ฉ๋ฉด ์ฌ๋ฌ๋ถ๋ค์, inline ์คํ์ผ์ด๊ธฐ ๋๋ฌธ์, ํ
์คํธ ํ ํ์๊ฐ ์๋ค๊ณ ์๊ฐํ ์ง๋ ๋ชจ๋ฆ
๋๋ค. ํ์ง๋ง, background-image
๋ wallpaperPath
Prop์ ๋ฐ๋ผ ๋ฐ๋๋ฏ๋ก ํ
์คํธ ๋ ํ์๊ฐ ์์ต๋๋ค. ๋ง์ฝ ํ
์คํธํ์ง ์๋๋ค๋ฉด, Component์ Public interface์ ์ผ๋ถ์ธ wallpaperPath
Prop์ ์ํฅ์๋ํ ํ
์คํธ๋ ์๋ฌด๊ฒ๋ ์๊ฒ ๋ฉ๋๋ค. Public interface๋ ํญ์ ํ
์คํธ ๋์ด์ ธ์ผ๋ง ํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋จ์ 2๊ฐ์ Constraint๋ค์ ๋ด ์๋ค:
- ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๋ช๊ฐ์ง inline ์คํ์ผ๊ณผ ํจ๊ปTopOverlay
์ ์์์ผ๋ก ๋๊ฒจ์ค๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
์ด๊ฒ๋ค๋ ๋ค์๊ณผ ๊ฐ์ด ๋๋ ์ ์์ต๋๋ค:
- ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด,TopOverlay
๋ฅผ ๋ ๋๋งํฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ทธ๊ฒ์TopOverlay
์ children์ผ๋ก ๋๊ฒจ์ ธ์ผ ํฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๋ ๋๋ง๋TopOverlay
๋ inline ์คํ์ผ์ ๊ฐ์ง๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
์ฒซ๋ฒ์งธ๊ณผ 4๋ฒ์งธ Constraint๋(TopOverlay
์ ๋ ๋๋ง ๋๋ค/๋์ง ์๋๋ค) ๋ฌด์์ ๋ ๋๋งํ ์ง๋ฅผ ๋งํ๊ณ ์์ผ๋ฏ๋ก, ํ
์คํธํฉ๋๋ค.
๋๋ฒ์งธ Constraint๋ TopOverlay
๊ฐ userInfoMessage
์ ๊ฐ์ Prop์ผ๋ก ๋ฐ๊ณ ์๋์ง๋ฅผ ํ์ธํฉ๋๋ค. ์ด ์ญ์ ๋ ๋๋ง๋ Component๊ฐ ๋ฐ์ Prop์ ๋ํ ๊ฒ์ด๋ฏ๋ก ํ
์คํธํ ๋งํผ ์ค์ํฉ๋๋ค. ๊ณ ๋ก, ์ด๊ฒ๋ ํ
์คํธํฉ๋๋ค.
3๋ฒ์งธ Constraint๋ TopOverlay
๊ฐ ๋ฐ๋ ํน์ Prop์ ํ์ธํ๊ธฐ ๋๋ฌธ์, ์ด์ฉ๋ฉด ํ
์คํธํด์ผ ํ๋ค๊ณ ์๊ฐํ์
จ์ ์๋ ์์ต๋๋ค. ํ์ง๋ง, ์ด Prop์ ๊ทธ์ inline ์คํ์ผ์
๋๋ค. Prop์ด ๋๊ฒจ์ก๋์ง ํ์ธํ๋ ๊ฒ์ ์ค์ํ์ง๋ง, inline ์คํ์ผ์ ๋ํ ํ
์คํธ๋ฅผ ๋ง๋๋๊ฑด ๋ง๊ฐ์ง๊ธฐ ์ฝ๊ณ ์คํ์ฝ๋์ ์ค๋ณต๋ฉ๋๋ค. (Rule #1์ ์๋ฐฐ๋ฉ๋๋ค) ๋๊ฒจ์ง Prop๋ค์ ํ์ธํ๋ ๊ฑด ์ค์ํ๊ธฐ ๋๋ฌธ์, Rule #1๋ง ์๊ฐํ๋ฉด ํ
์คํธ๋ฅผ ํด์ผํ ์ง ๋ง์ง๊ฐ ์ ๋งคํด์ง ์ ์์ต๋๋ค; ๋คํ์ค๋ฝ๊ฒ๋ ์ ๊ฐ Rule #3๋ฅผ ๋งํ ์ด์ ๊ฐ ์ฌ๊ธฐ์ ์์ต๋๋ค. ๋ค์ ์๊ธฐํ๋ฉด:
Component ๋ฐ๊นฅ์์์ ์์ ์์, ์ด๋ฌํ ์ธ๋ถํญ๋ชฉ๋ค์ด ์ค์ํ๊ฐ? ํน์ ๊ทธ์ ๋ด๋ถ์ ์ธ ์ฌ์ ์ธ๊ฐ? ์ด๋ฌํ ๋ด๋ถ์ ์ธ ์ฌ์ ์ผ๋ก ์ธํ ์ํฅ๋ Component์ public API๋ฅผ ์ฐ๋ ๊ฒ๋ง์ผ๋ก ์ค๋ช ๋ ์ ์๋๊ฐ?
ํ ์คํธ๋ฅผ ์ธ ๋, ์ ๋ ๊ฐ๋ฅํ Component์ public API๋ง์ ํ ์คํธํฉ๋๋ค. (์ดํ๋ฆฌ์ผ์ด์ API๊ฐ ๊ฐ์ง๋ Side effects๋ฅผ ํฌํจํด์) ์ด Component์ ๋ ์ด์์์ public API๋ก๋ถํฐ ์๋ฌด๋ฐ ์ํฅ์ ๋ฐ์ง ์์ต๋๋ค; ์ด๊ฒ์ CSS์์ง์ด ์ ๊ฒฝ ์ธ ์ผ์ ๋๋ค. ์ด๋ก ์ธํด Rule #3๊น์ง ์๋ฐํ๊ฒ ๋ฉ๋๋ค. ๊ฒฐ๊ตญ Rule #1์ Rule #3์ ์๋ฐฐ๋๋ฏ๋ก, TopOverlay๊ฐ Prop์ ๋ฐ๋์ง๋ฅผ ํ์ธํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ผ๋ก ์ค์ํ์ง๋ง, ํ ์คํธ ํ์ง ์์ต๋๋ค.
๋ง์ง๋ง Constraint๋ ํ ์คํธํ ์ง ๋ง์ง ๊ฒฐ์ ํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค. ๊ฒฐ๊ตญ ์ฌ๋ฌ๋ถ์ด ์ด๋ค ๋ถ๋ถ์ด ํ ์คํธํ ๋งํผ ์ค์ํ์ง์ ๋ฌ๋ ค์์ต๋๋ค; ์ด๋ฌํ Rules of Thumb๋ ๊ทธ์ ๊ฐ์ด๋๋ผ์ธ์ผ ๋ฟ์ ๋๋ค.
์ด์ ์ฐ๋ฆฐ ๋ชจ๋ Constraint๋ค์ ํ์ธํด๋ณด์๊ณ , ๋ฌด์์ ํ ์คํธํ ๊ฑด์ง ์๊ฒ ๋์์ต๋๋ค. ์ฌ๊ธฐ์ ๋ค์ ํ๋ฒ ๋ฆฌ์คํธ๋ฅผ ๋ณด์ฌ๋๋ฆฌ๊ฒ ์ต๋๋ค:
div
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.div
๋ ๋ ๋๋ง๋๋ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ๋ค์ ๋ด๊ณ ์์ต๋๋ค.ClockDisplay
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.- ๋ ๋๋ง๋
ClockDisplay
๋ ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค. SlideToUnlock
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.onUnlocked
Prop์ด ์ ์๋์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋กonUnlocked
๋ฅผ ๋ฐ์ต๋๋ค.onUnlocked
Prop์ด ์ ์๋์ง ์์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop ์ญ์undefined
๋ฅผ ๋ฐ์ต๋๋ค.- ๋ง์ฝ
wallpaperPath
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ฐ์ฅ ๋ฐ๊นฅ์div
๋ ๊ทธ ๊ฐ์ด ๋ฌด์์ด๋ url(...)๋ก ๊ฐ์ธ์ฌ์ ธ์background-image
CSS property๋ฅผ inline ์คํ์ผ๋ก ๊ฐ์ง๊ฒ ๋ฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด,TopOverlay
๋ฅผ ๋ ๋๋งํฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ทธ๊ฒ์TopOverlay
์ children์ผ๋ก ๋๊ฒจ์ ธ์ผ ํฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
Constraint๋ค์ ์ธ์ธํ๊ฒ ์ดํด๋ด์ผ๋ก์จ, ๋ง์ ์์ Constraint๋ฅผ ์ฌ๋ฌ ์์ Constraint๋ค๋ก ๋๋์์ต๋๋ค. ์ด๊ฑด ์ ๋ง ์ข์์! ์ด์ ํ ์คํธ ์ฝ๋๋ฅผ ์ฐ๊ธฐ๊ฐ ๋์ฑ ์ฌ์์ง ๊ฒ๋๋ค.
Componentํ ์คํธ๋ฅผ ์ํ ์ค๋น๋ฅผ ํด๋ด ์๋ค. ์ ๋ enzyme์ ํจ๊ป Jest๋ฅผ ์ธ๊ฒ๋๋ค. Jest๋ React์ ๋งค์ฐ ์ ๋ง๊ณ create-react-app์ผ๋ก ๋ง๋ค์ด์ง ์ดํ๋ฆฌ์ผ์ด์ ์๋ ํฌํจ๋ ํ ์คํธ๋ฌ๋์ ๋๋ค. ๊ทธ๋์ ์ด์ฉ๋ฉด ์ฌ๋ฌ๋ถ๋ค์ ์ด๋ฏธ ์ฌ์ฉํ ์ค๋น๊ฐ ๋์ด์์ ์๋ ์์ต๋๋ค. Enzyme๋ ๋ธ๋ผ์ฐ์ ์ Nodeํ๊ฒฝ์์ ๋์ํ๋ ๋ฏฟ์ ๋งํ React ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๋น๋ก ์ ๋ Jest์ enzyme๋ฅผ ์ฌ์ฉํ์ง๋ง, ์ฌ๊ธฐ์ ์ฐ์ธ ๊ฐ๋ ๋ค์ ์ด๋ค ํ ์คํธ ํ๊ฒฝ์์๋ ์ ์ฉ์ํค์ค ์ ์์ ๊ฒ๋๋ค.
LockScreen.test.jsx
import React from "react";
import { mount } from "enzyme";
import LockScreen from "./LockScreen";
describe("LockScreen", () => {
let props;
let mountedLockScreen;
const lockScreen = () => {
if (!mountedLockScreen) {
mountedLockScreen = mount(
<LockScreen {...props} />
);
}
return mountedLockScreen;
}
beforeEach(() => {
props = {
wallpaperPath: undefined,
userInfoMessage: undefined,
onUnlocked: undefined,
};
mountedLockScreen = undefined;
});
// ๋ชจ๋ ํ
์คํธ๋ค์ ์ฌ๊ธฐ์ ์ฐ์ฌ์ง ๊ฒ์
๋๋ค.
});
์กฐ๊ธ ํฐ ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ๋์๋ค์. ๋ญ ํ๊ณ ์๋์ง ์ค๋ช ํด๋๋ฆด๊ฒ์:
let
์ผ๋กprops
์mountedLockScreen
๋ฅผ ์ค์ ํฉ๋๋ค. ์ด๋ก์จdescribe
ํจ์ ์์ด๋ฉด ์ด๋์์๋ ๋ถ๋ฅผ ์ ์์ต๋๋ค.lockScreen
ํจ์ ์ญ์describe
์ ์ด๋์์๋ ๋ถ๋ฅผ ์ ์์ต๋๋ค. ์ด๊ฒ์mountedLockScreen
๋ณ์์ LockScreen์ Prop์ ํจ๊ผ Mountํด์ฃผ๊ณ , ์ด๋ฏธ Mount๋ ๊ฒ์ ๋๋ ค์ค๋๋ค. ์ด ํจ์๋ enzyme ReactWrapper๋ฅผ ๋ฆฌํดํฉ๋๋ค. ์ด๊ฑด ๋งค ํ ์คํธ๋ง๋ค ์ฌ์ฉํ ๊ฒ๋๋ค.beforeEach
๋props
์mountedLockScreen
์ ๋งค ํ ์คํธ๋ง๋ค ์ด๊ธฐํ์ํต๋๋ค. ํ์ง ์์ ๊ฒฝ์ฐ, ํ ํ ์คํธ์ ์ํ๊ฐ ๋ค๋ฅธ ํ ์คํธ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค. ์ฌ๊ธฐ์mountedLockScreen
๋ฅผundefined
๋ก ์ค์ ํจ์ผ๋ก์จ, ๋ค์ ํ ์คํธ๊ฐ ์คํ๋ ๋,lockScreen
์ ๋ถ๋ฅด๋ฉด ๋งค๋ฒ ์๋ก์ดLockScreen
์ดprops
์ ํจ๊ป Mount๋ ๊ฒ๋๋ค.
์ด ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ Component ํ๋๋ฅผ ํ ์คํธํ๊ธฐ์ ๋ง์๋ณด์ผ์ง๋ ๋ชจ๋ฆ ๋๋ค๋ง, Component๋ฅผ Mountํ๊ธฐ ์ ์ ์ถ๊ฐ์ ์ผ๋ก Props๋ฅผ ์ค๋นํด ์ค ์ ์์ต๋๋ค. ์ด๋ ํ ์คํธ๊ฐ Dryํ๋๋ก ๋์์ค๋๋ค. ์ ๋ ์ด๊ฒ์ ๋ชจ๋ Component ํ ์คํธ์ ์ฌ์ฉํ๊ณ , ์ฌ๋ฌ๋ถ๋ค์๊ฒ๋ ์ ์ฉํ๊ธธ ๋น๋๋ค; ์ ์ฉ์ฑ์ ์ด์ ํ ์คํธ ์ผ์ด์ค๋ค์ ์จ๋ณผ ์๋ก ๋์ฑ ํ์คํ๊ฒ ์๊ฒ ๋์ค ๊ฒ๋๋ค.
์ญ์ ์ฃผ: Dryํ ํ ์คํธ๋ ๋ค๋ฅธ ์ธ๋ถ ์์ธ๋ค์ด ํต์ ๋ ํ ์คํธ๋ฅผ ์๋ฏธํฉ๋๋ค.
Prop๋ค์ด ์ด๊ธฐํ๋์ง ์๋๋ค๋ฉด ์์ํ ํ ์คํธ์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ฐ๋ ์ ์์ต๋๋ค. ํ์ง๋ง ์ด๊ธฐํ๋ฅผ ์์ผ์ค๋ค๋ฉด ์์ ์ด๋ค ํ ์คํธ๊ฐ ํํด์ง๋ ์ง๊ธ ํ ์คํธ์๋ ์๋ฌด๋ฐ ์ํฅ์ด ์๊ฒ ๋๋ฏ๋ก ์ ๋ขฐ์ฑ ์๋ ํ ์คํธ๋ฅผ ํ๋๊ฒ ๊ฐ๋ฅํด์ง๋๋ค. ์ด๋ฅผ Dry ํ ์คํธ๋ผ๊ณ ํฉ๋๋ค.
Constraint๋ชฉ๋ก์ ๋ณด๋ฉฐ ํ๋์ฉ ํ
์คํธ๋ฅผ ์ถ๊ฐํด ๋ด
์๋ค. ๊ฐ๊ฐ์ ํ
์คํธ๋ ๋ณด์ผ๋ฌํ๋ ์ดํธ์ // All tests will go here
์ฝ๋ฉํธ ๋ค๋ก ์ถ๊ฐ๋๋ ํ์์ผ๋ก ์ฐ์ฌ์ ธ๋๊ฐ ๊ฒ์
๋๋ค.
div
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.
LockScreen.test.jsx
it("always renders a div", () => {
const divs = lockScreen().find("div");
expect(divs.length).toBeGreaterThan(0);
});
div
๋ ๋ ๋๋ง๋๋ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ๋ค์ ๋ด๊ณ ์์ต๋๋ค.
describe("the rendered div", () => {
it("contains everything else that gets rendered", () => {
const divs = lockScreen().find("div");
// .find๋ฅผ ์ฌ์ฉํ์ค ๋, enzyme๋ ๋
ธ๋๋ค์ ์ฐจ๋ก๋ก ๋์ดํด ์ค๋๋ค.
// ์ฌ๊ธฐ์ ๋ฐ๊นฅ์ชฝ๋ถํฐ ์ฝ์ด์ค๊ธฐ ๋๋ฌธ์ .first()๋ฅผ ์ฌ์ฉํ๋ฉด
// ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ div๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.
const wrappingDiv = divs.first();
// Enzyme๋ .children()๋ฅผ ์ฐ๋ฉด ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ Node๋ฅผ ์๋ตํด์ค๋๋ค.
// ์กฐ๊ธ ์ด๋ ค์ธ์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง, ์ด๊ฒ์ผ๋ก wrappingDiv๊ฐ ๋ค๋ฅธ ๋ชจ๋
// Component๋ฅผ ๊ฐ์ง๊ณ ์๋์ง ํ์ธ ํ ์ ์์ต๋๋ค.
expect(wrappingDiv.children()).toEqual(lockScreen().children());
});
});
ClockDisplay
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.
it("always renders a `ClockDisplay`", () => {
expect(lockScreen().find(ClockDisplay).length).toBe(1);
});
- ๋ ๋๋ง๋
ClockDisplay
๋ ์๋ฌด๋ฐ Prop๋ ๋ฐ์ง ์์ต๋๋ค.
describe("rendered `ClockDisplay`", () => {
it("does not receive any props", () => {
const clockDisplay = lockScreen().find(ClockDisplay);
expect(Object.keys(clockDisplay.props()).length).toBe(0);
});
});
SlideToUnlock
๋ ํญ์ ๋ ๋๋ง ๋ฉ๋๋ค.
it("always renders a `SlideToUnlock`", () => {
expect(lockScreen().find(SlideToUnlock).length).toBe(1);
});
์ฌ๊ธฐ๊น์ง ๋ชจ๋ Constraint๋ค์ ํญ์ ์ฐธ์ด ๋๋ ๊ฒ๋ค์ด๋ฏ๋ก ๋น๊ต์ ํ
์คํธ๋ฅผ ๋ง๋ค๊ธฐ๊ฐ ์ฌ์ ์ต๋๋ค. ๊ทธ๋ ์ง๋ง, ๋๋จธ์ง Constraint๋ค์ "๋ง์ฝ ... ํ๋ค๋ฉด" ๊ฐ์ ์กฐ๊ฑด์ด๋ ๋์ ํจ๊ปํฉ๋๋ค. ์ด๋ฌํ ๊ฒ๋ค์ ์กฐ๊ฑด์ ์ผ๋ก ์ฐธ์ด ๋๋ฏ๋ก beforeEach
์ ํจ๊ป 2๊ฐ์ describe
๋ฅผ ์ค๋นํ ๊ฒ๋๋ค.
onUnlocked
Prop์ด ์ ์๋์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop์ผ๋กonUnlocked
๋ฅผ ๋ฐ์ต๋๋ค.onUnlocked
Prop์ด ์ ์๋์ง ์์์ผ๋ฉด, ๋ ๋๋ง๋SlideToUnlock
์onSlide
Prop ์ญ์undefined
๋ฅผ ๋ฐ์ต๋๋ค.
describe("when `onUnlocked` is defined", () => {
beforeEach(() => {
props.onUnlocked = jest.fn();
});
it("sets the rendered `SlideToUnlock`'s `onSlide` prop to the same value as `onUnlocked`'", () => {
const slideToUnlock = lockScreen().find(SlideToUnlock);
expect(slideToUnlock.props().onSlide).toBe(props.onUnlocked);
});
});
describe("when `onUnlocked` is undefined", () => {
beforeEach(() => {
props.onUnlocked = undefined;
});
it("sets the rendered `SlideToUnlock`'s `onSlide` prop to undefined'", () => {
const slideToUnlock = lockScreen().find(SlideToUnlock);
expect(slideToUnlock.props().onSlide).not.toBeDefined();
});
});
์ด๋ค ์กฐ๊ฑด์์๋ง ์ผ์ด๋๋ ๋์์ ๋ฌ์ฌํ ๋, ๊ทธ ์กฐ๊ฑด์ ๋ํ ์ค๋ช ์ describe์ ๋ฃ์ด์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ , beforeEach๋ฅผ ํตํด ๊ทธ ์กฐ๊ฑด์ ์ค์ ํด์ค๋๋ค.
์ญ์ ์ฃผ:
describe
์๋ ํ ์คํธํ๋ ค๋ ์กฐ๊ฑด์ ๋ํ ์๊ธฐ๊ฐ,it
์ ๊ทธ ์กฐ๊ฑด์์ ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ์ป์ด์ง์ง์ ๋ํ ์ค๋ช ์ด ๋ค์ด๊ฐ์์ต๋๋ค. ํ ์คํธ ์ฝ๋๋ ์์ด ๋ฌธ๋ฒ์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ์ฝํ๊ธฐ์ ์ผ๋ถ๋ฌ ๋ฒ์ญํ์ง ์์์ง๋ง, ํ๊ตญ์ด๋ก ์ฐ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ฉ๋๋ค.describe("`onUnlocked` Prop์ด ์ ์๋์์ผ๋ฉด", () => { beforeEach(() => { props.onUnlocked = jest.fn(); }); it("๋ ๋๋ง๋ `SlideToUnlock`์ `onSlide` Prop์ผ๋ก `onUnlocked`๋ฅผ ๋ฐ์ต๋๋ค.", () => { const slideToUnlock = lockScreen().find(SlideToUnlock); expect(slideToUnlock.props().onSlide).toBe(props.onUnlocked); }); }); describe("`onUnlocked` Prop์ด ์ ์๋์ง ์์์ผ๋ฉด", () => { beforeEach(() => { props.onUnlocked = undefined; }); it("๋ ๋๋ง๋ `SlideToUnlock`์ `onSlide` Prop ์ญ์ `undefined`๋ฅผ ๋ฐ์ต๋๋ค.", () => { const slideToUnlock = lockScreen().find(SlideToUnlock); expect(slideToUnlock.props().onSlide).not.toBeDefined(); }); });
- ๋ง์ฝ
wallpaperPath
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ฐ์ฅ ๋ฐ๊นฅ์div
๋ ๊ทธ ๊ฐ์ด ๋ฌด์์ด๋url(...)
๋ก ๊ฐ์ธ์ฌ์ ธ์background-image
CSS property๋ฅผ inline ์คํ์ผ๋ก ๊ฐ์ง๊ฒ ๋ฉ๋๋ค.
describe("when `wallpaperPath` is passed", () => {
beforeEach(() => {
props.wallpaperPath = "some/image.png";
});
it("applies that wallpaper as a background-image on the wrapping div", () => {
const wrappingDiv = lockScreen().find("div").first();
expect(wrappingDiv.props().style.backgroundImage).toBe(`url(${props.wallpaperPath})`);
});
});
- ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด,TopOverlay
๋ฅผ ๋ ๋๋งํฉ๋๋ค. - ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง๋ฉด, ๊ทธ๊ฒ์TopOverlay
์ children์ผ๋ก ๋๊ฒจ์ ธ์ผ ํฉ๋๋ค.
describe("when `userInfoMessage` is passed", () => {
beforeEach(() => {
props.userInfoMessage = "This is my favorite phone!";
});
it("renders a `TopOverlay`", () => {
expect(lockScreen().find(TopOverlay).length).toBe(1);
});
it("passes `userInfoMessage` to the rendered `TopOverlay` as `children`", () => {
const topOverlay = lockScreen().find(TopOverlay);
expect(topOverlay.props().children).toBe(props.userInfoMessage);
});
});
- ๋ง์ฝ
userInfoMessage
Prop์ด ๋๊ฒจ์ง์ง ์๋๋ค๋ฉด,TopOverlay
๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค.
describe("when `userInfoMessage` is undefined", () => {
beforeEach(() => {
props.userInfoMessage = undefined;
});
it("does not render a `TopOverlay`", () => {
expect(lockScreen().find(TopOverlay).length).toBe(0);
});
});
์ด๊ฑธ๋ก ๋ชจ๋ Constraint์ ํ ์คํธ๊ฐ ์์ฑ๋์์ต๋๋ค! ์ต์ข ํ ์คํธ ํ์ผ์ ์ฌ๊ธฐ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
๊ธ ์ฒ์์ Gif์ ๋ณด์ฌ๋๋ ธ์ ๋, ์ฌ๋ฌ๋ถ์ ํ ์คํธ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ์์ฑ๋ ๊ฑฐ๋ผ ์์ํ์ จ๋ ๋ถ๋ค์ด ์์ผ์ จ์ ๊ฒ๋๋ค:
- ์ ์ ๊ฐ ๋ฐ์ด์ ์ ๊ธํด์ ๋ฅผ ๋๋๊ทธ ์ค๋ฅธ์ชฝ ๋๊น์ง ํ๋ฉด, unlock ์ฝ๋ฐฑ์ด ๋ถ๋ฌ์ง๋ค.
- ๋๋๊ทธํ๋ ๋์ค์ ํธ๋ค์ ๋์๋ฒ๋ฆฌ๋ฉด, ํธ๋ค์ ์๋ ์์น๋ก ๋์๊ฐ๋ ์ ๋๋ฉ์ด์ ์ ๋ณด์ฌ์ค๋ค.
- ์คํฌ๋ฆฐ ์ต์๋จ์ ์๊ณ๋ ์ธ์ ๋ ํ์ฌ ์๊ฐ์ ๋ณด์ฌ์ค๋ค.
์ด๋ฌํ ์๊ฐ๋ค์ ์์ฐ์ค๋ฌ์ด ๊ฒ๋๋ค. ์ดํ๋ฆฌ์ผ์ด์ ์ ์ ์ฒด์ ์ธ ์์ ์์ ๋ณด๋ฉด ๋งค์ฐ ๋น์ฐํ ๊ฒ๋ค์ ๋๋ค.
ํ์ง๋ง ์ฐ๋ฆฌ๋ ์ด์ ๋ํด ์๋ฌด๋ฐ ํ
์คํธ๋ ์ฐ์ง ์์์ต๋๋ค. ์๋๊ณ ์? ์ด๊ฑด LockScreen
์ด ์ ๊ฒฝ์ธ ๊ฒ์ด ์๋๋๊น์.
React Coponent๊ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฑด, Unit ํ ์คํธ๋ค๊ณผ ์์ฐ์ค๋ฝ๊ฒ ์ด์ธ๋ฆด ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฐ๋ฆฌ๊ฐ ๊ทธ๋ฆฌ๊ณ Unit ํ ์คํธ๋ฅผ ํ ๋, ํด๋น Unit์ด ์ ๊ฒฝ์จ์ผ ํ ๊ฒ๋ง ํ ์คํธ ํ์ ์ผ ๋ฉ๋๋ค. React Component ํ ์คํธ๋ฅผ ํ ๋ ์ฒ์ ๋ณด๋ ๊ฒ๋ณด๋ค ๊ฐ๊ฐ์ ๋๋ฌด๋ฅผ ์ ๊ฒฝ์ฐ์๋๊ฒ ๋ ์ข์ต๋๋ค.
๋๋ถ๋ถ์ React Component๋ค์ด ์ ๊ฒฝ์จ์ผ ํ ๊ฒ๋ค์ ์ค๋ช ํ๋ ์ปจ๋ ํ์ดํผ๋ฅผ ๋๋ฆฌ๊ฒ ์ต๋๋ค:
- ๋ฐ์ Prop์ผ๋ก ๋ญ ํ ๊ฑด๊ฐ?
- ์ด๋ค Component๋ค์ ๋ ๋๋งํด์ผํ๋? ๋ ๋๋ง๋ Component๋ค์๊ฒ ๋ฌด์์ ๋๊ฒจ์ฃผ์ด์ผ ํ๋๊ฐ?
- State๋ฅผ ๊ฐ์ง ํ์๊ฐ ์๋? ํน์ ๊ทธ๋ ๋ค๋ฉด, ์ Prop์ ๋ฐ์์ ๋ ์ด๊ธฐํ ์์ผ์ผ ํ๋๊ฐ? ์ธ์ State๋ฅผ ์ ๋ฐ์ดํธ ํด์ผํ๋?
- ๋ง์ฝ ํด๋น Component๋ Child Component๋ก ๋๊ฒจ์ค Callback์ด ์ ์ ์ ์ํธ์์ฉ์ผ๋ก ๋ถ๋ฌ์์ง๋ค๋ฉด Component๋ ๋ฌด์์ ํด์ผ ํ๋๊ฐ?
- Mount๋์์ ๋ ๋ฌด์์ด ์ผ์ด๋๋๊ฐ? Unmount๋์์ ๋๋?
๊ณ ๋ก, ์์ ๋งํ ๊ธฐ๋ฅ๋ค์ SlideToUnlock
์ ClockDisplay
๊ฐ ์ ๊ฒฝ์จ์ผํ ๊ฒ์ด๋ฏ๋ก ์ฌ๊ธฐ๊ฐ ์๋๋ผ ๊ฐ๊ฐ์ Component์ ํ
์คํธ๋ก ๋ง๋ค์ด์ผ ํ ๊ฒ๋๋ค.
์ด๋ฐ ๋ฐฉ๋ฒ๋ค์ด ์ฌ๋ฌ๋ถ์ด React Component ํ ์คํธ๋ฅผ ์์ฑํ๋๋ฐ ๋์์ด ๋๊ธธ ๋น๋๋ค. ์์ฝํ์๋ฉด:
- ์ฐ์ Component์ Contract๋ฅผ ํ์ ํด๋ผ
- ์ด๋ค Constraint๊ฐ ํ ์คํธํ ๊ฐ์น๊ฐ ์๋์ง ์ ํด๋ผ.
- Prop ํ์ ๋ค์ ํ ์คํธํ ํ์๊ฐ ์๋ค.
- Inline ์คํ์ผ๋ค์ ์ผ๋ฐ์ ์ผ๋ก ํ ์คํธํ ํ์๊ฐ ์๋ค.
- ๋ ๋๋งํ๋ ค๋ Component๋ค๊ณผ ๊ทธ๊ฒ๋ค์๊ฒ ๋๊ฒจ์ฃผ๋ Prop๋ค์ ํ ์คํธํด์ผ ํ ๋งํผ ์ค์ํ๋ค.
- ํด๋น Component๊ฐ ์ ๊ฒฝ์ธ๊ฒ ์๋ ๊ฒ์ ํ ์คํธ ํ์ง ๋ง๋ผ.
๋ง์ฝ ๋ค๋ฅธ ์๊ฐ์ด ์์ผ์๊ฑฐ๋, ์ด ํฌ์คํธ๊ฐ ๋์์ด ๋์ ง๋ค๋ฉด, Twitter๋ก ์๊ธฐํด์ฃผ์ธ์. React Component๋ฅผ ์ด๋ป๊ฒ ํ ์คํธํ๋์ง ๋ชจ๋ ํจ๊ป ๋ฐฐ์ธ ์ ์์๊ฑฐ์์.
Stephen Scott๋์ ํตํฉ ์ค๋งํธํ ์๋ํ ์์คํ ์ ๋ง๋๋ Nexia์ ๊ฐ๋ฐ์์ ๋๋ค. Nexia๋ ๊ณ ์ฉ์ค์ ๋๋ค! Colorado์ฃผ Broomfield์ ์๋ ์ฌ๋ฌด์ค์์๋ ์ ํฌ ๊ฐ๋ฐ์ ํ์ ํจ๊ปํ๊ณ ์ถ์ผ์๋ค๋ฉด Twitter๋ก ์๋ ค์ฃผ์ธ์.
์ด ๊ธ์ ์ ์๊ถ์ ๋ณดํธ๋ฐ๊ณ ์์ง๋ง, ์ด ํฌ์คํ ์ ๋ชจ๋ ์ํ ์ฝ๋๋ค์ MIT ๋ผ์ด์ผ์คํ์ ๊ณต๊ฐ๋์ด ์์ต๋๋ค. GitHub์์ ์ฐพ์ผ์ค ์ ์์ต๋๋ค.