E2E 테스트를 작성하면서 이 로직을 테스트(검증)하는 것 외 얻었던 장점이 있었다. 바로 테스트 코드가 문서화 역할을 할 수 있다는 것. Cypress Command를 활용한 선언적 테스트 작성은 이 컴포넌트가 어떤 역할을 하는지, 이 기능이 어떤 기능을 가지고 있는지 설명 할 수 있는 가이드라인이 됐다.
이번에도 React-Testing-Library를 사용하며 테스트를 작성하는데 선언적인 테스트 작성으로 이 컴포넌트의 가이드라인을 제공해주고 싶었다. 그래서 아래와 같이 고도화된 render 함수를 사용했다.
describe('<CartListItem />', () => {
const cartItem = createCartItem();
const carts = [createCartItem(0), createCartItem(1)];
const VALID_CART_ITEM_QUANTITY = 10;
const MIN_CART_ITEM_QUANTITY = 1;
const MAX_CART_ITEM_QUANTITY = 20;
const cartItemHasValidQuantity =
createCartItem(1, VALID_CART_ITEM_QUANTITY);
const cartItemHasMinQuantity =
createCartItem(1, MIN_CART_ITEM_QUANTITY);
const cartItemHasMaxQuantity =
createCartItem(1, MAX_CART_ITEM_QUANTITY);
beforeEach(() => {
jest.clearAllMocks();
(useDispatch as jest.Mock).mockImplementation(() => useAppDispatch);
(useSelector as jest.Mock).mockImplementation(() => useAppSelector);
});
it('장바구니에 담긴 아이템 정보를 보여준다.', () => {
const { ProductName, ProductPrice, CartItemQuantity, IncreaseQuantityButton, DecreaseQuantityButton } =
renderCartListItem(cartItem, carts);
expect(ProductName()).toBeInTheDocument();
expect(ProductPrice()).toBeInTheDocument();
expect(CartItemQuantity()).toBeInTheDocument();
expect(IncreaseQuantityButton()).toBeInTheDocument();
expect(DecreaseQuantityButton()).toBeInTheDocument();
});
it('장바구니 수량이 20개 미만 일때 버튼(+)을 누르면 액션이 dispatch 된다.', async () => {
const { clickIncreaseQuantityButton, calledDispatch } = renderCartListItem(cartItemHasValidQuantity, carts);
clickIncreaseQuantityButton();
calledDispatch();
});
it('장바구니 수량이 20개인 경우 버튼(+)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const { clickIncreaseQuantityButton, notCalledDispatch } = renderCartListItem(cartItemHasMaxQuantity, carts);
clickIncreaseQuantityButton();
notCalledDispatch();
});
it('장바구니 수량이 1개를 초과하는 경우 버튼(-)을 누르면 액션이 dispatch 된다.', () => {
const { clickDecreaseQuantityButton, calledDispatch } = renderCartListItem(cartItemHasValidQuantity, carts);
clickDecreaseQuantityButton();
calledDispatch();
});
it('장바구니 수량이 1개인 경우 버튼(-)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const { clickDecreaseQuantityButton, notCalledDispatch } = renderCartListItem(cartItemHasMinQuantity, carts);
clickDecreaseQuantityButton();
notCalledDispatch();
});
});
이 함수로 얻을 수 있었던 점은 크게 두개였던 것 같다.
result.getByText(’담기버튼')
와 같은 로직이 담기지 않음. 그냥 expect(AddCartButton()).toBeInTheDocument()
와 같이 작업 할 수 있음getByText()
, getByRole
하는 작업을 줄여줄 수 있음위와 같은 장점을 가질 수 있는 render 함수로 아래와 같이 테스트를 작성했다.
describe('<CartListItem />', () => {
const cartItem = createCartItem();
const carts = [createCartItem(0), createCartItem(1)];
const VALID_CART_ITEM_QUANTITY = 10;
const MIN_CART_ITEM_QUANTITY = 1;
const MAX_CART_ITEM_QUANTITY = 20;
const cartItemHasValidQuantity = createCartItem(1, VALID_CART_ITEM_QUANTITY);
const cartItemHasMinQuantity = createCartItem(1, MIN_CART_ITEM_QUANTITY);
const cartItemHasMaxQuantity = createCartItem(1, MAX_CART_ITEM_QUANTITY);
beforeEach(() => {
jest.clearAllMocks();
(useDispatch as jest.Mock).mockImplementation(() => useAppDispatch);
(useSelector as jest.Mock).mockImplementation(() => useAppSelector);
});
it('장바구니에 담긴 아이템 정보를 보여준다.', () => {
const { ProductName, ProductPrice, CartItemQuantity, IncreaseQuantityButton, DecreaseQuantityButton } =
renderCartListItem(cartItem, carts);
expect(ProductName()).toBeInTheDocument();
expect(ProductPrice()).toBeInTheDocument();
expect(CartItemQuantity()).toBeInTheDocument();
expect(IncreaseQuantityButton()).toBeInTheDocument();
expect(DecreaseQuantityButton()).toBeInTheDocument();
});
it('장바구니 수량이 20개 미만 일때 버튼(+)을 누르면 액션이 dispatch 된다.', async () => {
const { clickIncreaseQuantityButton, calledDispatch } = renderCartListItem(cartItemHasValidQuantity, carts);
clickIncreaseQuantityButton();
calledDispatch();
});
it('장바구니 수량이 20개인 경우 버튼(+)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const { clickIncreaseQuantityButton, notCalledDispatch } = renderCartListItem(cartItemHasMaxQuantity, carts);
clickIncreaseQuantityButton();
notCalledDispatch();
});
it('장바구니 수량이 1개를 초과하는 경우 버튼(-)을 누르면 액션이 dispatch 된다.', () => {
const { clickDecreaseQuantityButton, calledDispatch } = renderCartListItem(cartItemHasValidQuantity, carts);
clickDecreaseQuantityButton();
calledDispatch();
});
it('장바구니 수량이 1개인 경우 버튼(-)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const { clickDecreaseQuantityButton, notCalledDispatch } = renderCartListItem(cartItemHasMinQuantity, carts);
clickDecreaseQuantityButton();
notCalledDispatch();
});
});
확실히 고도화된 render 함수를 사용하기 전 보다 테스트가 선언적이게 되었고, 기능을 어느 정도 더 보기 쉽게 해주는 것 같다. 크게 비교해보면 전과 후는 이렇게 다르다.
it('장바구니 수량이 1개인 경우 버튼(-)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const onClickCartItemSelectButton = jest.fn();
const result = render(
<CartListItem
cartItem={cartItemHasMinQuantity}
onClickCartItemSelectButton={onClickCartItemSelectButton}
selectedCartItems={carts}
/>,
);
userEvent.click(result.getByText('+'));
expect(useDispatch).not.toBeCalled();
});
it('장바구니 수량이 1개인 경우 버튼(-)을 누르면 액션이 dispatch 되지 않는다.', async () => {
const { clickDecreaseQuantityButton, notCalledDispatch }
= renderCartListItem(cartItemHasMinQuantity, carts);
clickDecreaseQuantityButton();
notCalledDispatch();
});
});
확실히 정말 어떤 것을 테스트하는 것인지 테스트 내역만 보고도 이해 할 수 있다. 분명 좋은 것 같은데 그럼에도 불구하고 아래와 같은 의문도 든다..
<aside> 💡 테스트는 로직을 검증하기 위한 도구라고 생각 할 수 있는데 그 안에 또 다시 로직을 담는 것이 맞을까?
</aside>