7주차에는 로그인 기능을 만드는 것을 배웠다.
레스토랑 상세 페이지 리뷰를 작성하려면 로그인을 한 상태에서 작성할 수 있도록 만드는 것이었다.
로그인 기능을 만들기 위해서는 로그인 정보를 서버에 보내는 과정이 필요
HTTP POST method를 이용
export async function postLogin({ email, password }) {
const url = 'https://eatgo-login-api.ahastudio.com/session';
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
const { accessToken } = await response.json();
return accessToken;
}
HTTP Authorization 요청 헤더는 서버의 사용자 에이전트임을 증명하는 자격을 포함한다
export async function postReview({
accessToken,
restaurantId,
score,
description,
}) {
const url = `https://eatgo-customer-api.ahastudio.com/restaurants/${restaurantId}/reviews`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ score, description }),
});
await response.json();
}
테스트 파일에서 Login 버튼을 누를 때, dispatch가 호출이 됐는지 확인을 테스트 할 때, 어느 것이 먼저 테스트가 될 지 확신하지 못하기 때문에 dispatch.mockClear()
를 해줘야 된다.
mockFn.mockClear()
: 각 테스트 전에 모의 실행을 자동으로 지울 수 있다.describe('LoginForm', () => {
const handleChange = jest.fn();
const handleSubmit = jest.fn();
beforeEach(() => {
handleChange.mockClear();
handleSubmit.mockClear();
});
function renderLoginForm({ email, password } = {}) {
return render(
<LoginForm
fields={{ email, password }}
onSubmit={handleSubmit}
onChange={handleChange}
/>,
);
}
it('renders input controls', () => {
const email = 'test@test';
const password = '1234';
const { getByLabelText } = renderLoginForm({ email, password });
const controls = [
{
label: 'E-mail',
value: email,
},
{
label: 'Password',
value: password,
},
];
controls.forEach(({ label, value }) => {
const input = getByLabelText(label);
expect(input.value).toBe(value);
});
});
it('listens change events', () => {
const email = 'test@test';
const password = '1234';
const { getByLabelText } = renderLoginForm({ email, password });
const controls = [
{
label: 'E-mail',
name: 'email',
value: 'tester@example.com',
},
{
label: 'Password',
name: 'password',
value: 'test',
},
];
controls.forEach(({ label, name, value }) => {
const input = getByLabelText(label);
fireEvent.change(input, { target: { value } });
expect(handleChange).toBeCalledWith({ name, value });
});
});
it('renders "Log In" button', () => {
const { getByText } = renderLoginForm();
expect(getByText('Log In')).not.toBeNull();
fireEvent.click(getByText('Log In'));
expect(handleSubmit).toBeCalled();
});
});
function renderLoginForm({ email, password } = {}) {
return render(
<LoginForm fields={{ email, password }} onSubmit={handleSubmit} onChange={handleChange} />,
);
}
// App.test.jsx
import { loadItem } from './services/storage';
jest.mock('react-redux');
jest.mock('./services/storage');
describe('App', () => {
const dispatch = jest.fn();
beforeEach(() => {
dispatch.mockClear();
useDispatch.mockImplementation(() => dispatch);
useSelector.mockImplementation((selector) => selector({
regions: [
{ id: 1, name: '서울' },
],
categories: [],
restaurants: [],
restaurant: { id: 1, name: '마녀주방' },
}));
});
function renderApp({ path }) {
return render(
<MemoryRouter initialEntries={[path]}>
<App />
</MemoryRouter>,
);
}
context('로그인을 할 경우', () => {
beforeEach(() => {
loadItem.mockImplementation(() => 'ACCESS_TOKEN');
});
it('dispatch를 호출한다.', () => {
renderApp({ path: '/' });
expect(dispatch).toBeCalledWith({
type: 'setAccessToken',
payload: { 'ACCESS_TOKEN' },
});
});
});
context('로그아웃을 할 경우', () => {
beforeEach(() => {
loadItem.mockImplementation(() => null);
});
it('dispatch가 호출되지 않는다.', () => {
renderApp({ path: '/' });
expect(dispatch).not.toBeCalled();
});
});
});
7주차 피드백에는 expect 구문을 빠트리고 테스트를 작성했다는 피드백을 많이 받았다. 점점 이런 단순한 실수를 많이 하게 됐다.
그리고 또 비동기 테스트를 제대로 하지 못했다. api 테스트랑 storage, 비동기 action 테스트를 완료하지 못했다. mocking에 대한 개념이 아직 많이 부족해서 이런 부분에서 테스트를 완수하지 못하게 되는 듯 싶다. mocking에 대한 부분을 보완해야 되겠다.