
import { useEffect, useState } from 'react';
interface User {
firstName: string;
lastName: string;
email: string;
}
export default function BadUserProfile() {
const [user, setUser] = useState<User | null>(null);
// [문제 1] 컴포넌트가 '데이터 가져오기'를 직접 수행함 (의존성 높음)
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users/1')
.then((res) => res.json())
.then((data) => {
// [문제 2] 데이터를 변환하는 '비즈니스 로직'이 섞여 있음
const formattedData = {
...data,
// 이름은 대문자로 변환해야 한다는 규칙
firstName: data.name.split(' ')[0].toUpperCase(),
lastName: data.name.split(' ')[1].toUpperCase(),
};
setUser(formattedData);
});
}, []);
if (!user) return <div>로딩 중...</div>;
return (
<div>
{/* [문제 3] 화면 그리기 로직 (UI) */}
<h1>{user.firstName} {user.lastName}</h1>
<p>{user.email}</p>
</div>
);
}fetch,还得等待异步处理(findBy)。
即使只是想确认“姓名是否以粗体(h1)显示”,也得这样做。props 接收并展示。
// 데이터 타입 정의
export interface UserProfileProps {
firstName: string;
lastName: string;
email: string;
}
export default function UserProfileView({ firstName, lastName, email }: UserProfileProps) {
return (
<div>
<h1>{firstName} {lastName}</h1>
<p>{email}</p>
</div>
);
}import { useEffect, useState } from 'react';
import UserProfileView from './UserProfileView';
export default function UserProfileContainer() {
const [user, setUser] = useState(null);
useEffect(() => {
// ... fetch 및 데이터 가공 로직 (혹은 Custom Hook 사용)
// 여기서는 생략합니다.
}, []);
if (!user) return <div>로딩 중...</div>;
// 가공된 데이터를 UI 컴포넌트에 주입
return <UserProfileView {...user} />;
}UserProfileView)。
完全不需要 fetch 或 MSW 等复杂配置。import { render, screen } from '@testing-library/react';
import { describe, test, expect } from 'vitest';
import UserProfileView from './UserProfileView';
describe('UserProfileView UI 테스트', () => {
test('props로 받은 이름과 이메일을 정확히 표시한다', () => {
// 1. 가짜 데이터(Mock Data) 준비
// API 호출 없이 우리가 원하는 데이터를 직접 주입합니다.
const mockUser = {
firstName: 'KIM',
lastName: 'CHULSU',
email: 'test@example.com'
};
render(<UserProfileView {...mockUser} />);
// 2. 로그 확인
console.log('[UI Test] 순수 컴포넌트 렌더링 확인');
screen.debug();
// 3. 검증
// h1 태그 안에 이름이 들어있는지 확인
expect(screen.getByRole('heading')).toHaveTextContent('KIM CHULSU');
// 이메일이 문서에 존재하는지 확인
expect(screen.getByText('test@example.com')).toBeInTheDocument();
});
});npm test -- src/components/UserProfileView.test.tsx
[UI Test] 순수 컴포넌트 렌더링 확인
<body>
<div>
<div>
<h1>
KIM CHULSU
</h1>
<p>
test@example.com
</p>
</div>
</div>
</body>
PASS src/components/UserProfileView.test.tsxfetch 的加载状态或错误信息,注入的 KIM CHULSU 数据直接清晰地渲染出来。await findBy...),测试瞬间完成。git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part11下一篇将通过第 12 篇:支持重构的测试组织方式,介绍如何编写在设计或内部实现改变后仍不会失效的测试。