[코드숨 리액트 13기] 7주차 과제 피드백

박세진·2023년 3월 4일
0

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() : 각 테스트 전에 모의 실행을 자동으로 지울 수 있다.
  • LoginForm.test.jsx 파일에서 controls를 배열로 만들어서 테스트 할 수 있다.
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();
  });
});

피드백

  • props가 3개 이상일 경우, 가로 정렬로 정리해주는 게 가독성을 높여준다.
  function renderLoginForm({ email, password } = {}) {
    return render(
      <LoginForm fields={{ email, password }} onSubmit={handleSubmit} onChange={handleChange} />,
    );
  }
  • App 컴포넌트에서 accessToken이 있을 때, dispatch가 호출되는지 테스트를 하려고 했는데, 호출이 한번도 안됐다고 나오는 이유는 accessToken이 useSelector에서 가져오는게 아닌, localStorage에서 가져오기 때문이다. 그렇기 때문에 loadItem을 mocking 해줘야 된다. (redux mock store를 사용)
// 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에 대한 부분을 보완해야 되겠다.

profile
경험한 것을 기록

0개의 댓글