묻고 답해요
167만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 2부. 테스트 심화: 시각적 회귀・E2E 테스트
AvailableUsers
시각적 회귀 테스트 강의의 Storybook 코드에서 play 함수 안에 matcher를 넣지 않고 userEvent.click(...)만 사용한 이유가 궁금합니다.import { userEvent, within } from '@storybook/testing-library'; import AvailableUsers from '@/pages/login/components/AvailableUsers'; export default { component: AvailableUsers, title: '로그인/사용자 리스트', }; export const Folded = { name: '접힌 상태', }; export const Expanded = { name: '펼친 상태', play: async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByText('⚠️ 사용 가능한 유저 리스트')); }, }; 제가 이해한 바로는, 이 스토리는 storybook에서 아코디언이 펼쳐진 시각적 상태 변화를 보여주기 위한 용도이고, 실제 기능 로직 검증은 Vitest에서 별도의 테스트 코드로 작성하는 방향이라고 생각했습니다. 1. 여기서 play 안에 matcher를 넣지 않은 것은 Storybook을 시각적 상태 확인용으로 사용하려는 의도인가요? 2. 그리고 이 컴포넌트의 동작 검증은 Storybook이 아니라 Vitest에서 담당하도록 역할을 나눈 것으로 이해하면 될까요?3. 실무에서는 보통 storybook을 어디까지 작성하는지 궁금합니다. - 물론 회사마다 다르겠지만, 강의해주시는 강사님 기준으로 의견을 듣고 싶습니다.
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 2부. 테스트 심화: 시각적 회귀・E2E 테스트
storybook/ addon react-router-dom
// package.json npm list로 확인 결과 react-router@7.13.1 storybook-addon-remix-react-router@6.1.0 react@19.2.4 storybook@10.2.17강의에 있는 내용을 토대로 typescript로 쇼핑몰 storybook 테스트를 진행 중에 React Router pannel에 로깅이 찍히지 않습니다. github, storybook docs 참고하면서 해봐도 해결이 되지를 않는데... 제 코드는 아래와 같습니다..storybook/main.ts// .storybook/main.ts import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], addons: [ "@chromatic-com/storybook", "@storybook/addon-vitest", "@storybook/addon-a11y", "@storybook/addon-docs", "storybook-addon-remix-react-router", ], framework: "@storybook/react-vite", }; export default config; .storybook/preview.tsximport type { Preview } from "@storybook/react-vite"; import { withRouter } from "storybook-addon-remix-react-router"; const preview: Preview = { tags: ["autodocs"], parameters: { controls: { matchers: { color: /(background|color)$/i, date: /Date$/i, }, }, a11y: { // 'todo' - show a11y violations in the test UI only // 'error' - fail CI on a11y violations // 'off' - skip a11y checks entirely test: "todo", }, }, decorators: [withRouter], }; export default preview; EmptyNotice.tsximport type { MouseEvent } from "react"; import { useNavigate } from "react-router"; const EmptyNotice = () => { const navigate = useNavigate(); const handleClickBack = (event: MouseEvent<HTMLButtonElement>) => { event.preventDefault(); navigate("/"); }; return ( <div style={{ display: "flex", justifyContent: "center", height: 400, alignItems: "center", flexDirection: "column", }} > <p style={{ fontSize: "50px", fontWeight: 300 }}>텅~</p> <button onClick={handleClickBack} style={{ cursor: "pointer" }}> 홈으로 가기 </button> </div> ); }; export default EmptyNotice; EmptyNotice.stories.tsximport type { Meta, StoryObj } from "@storybook/react-vite"; import EmptyNotice from "./EmptyNotice"; const meta = { title: "장바구니/EmptyNotice", component: EmptyNotice, } satisfies Meta<typeof EmptyNotice>; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = { name: "장바구니가 빈 경우", }; AI하고 문답하면서 디버깅 하는데 좀처럼 원인을 못찾겠어서 질문올립니다.
-
해결됨실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
mockZustand
안녕하세요.강의 너무 잘 보고 있습니다. 한 가지 궁금한 점이 있어서 QnA남깁니다.// __mocks__/zustand.js const { create: actualCreate } = await vi.importActual('zustand'); import { act } from '@testing-library/react' const storeResetFns = new Set(); export const create = createState => { const store = actualCreate(createState); const initialState = store.getState(); storeResetFns.add(()=>store.setState(initialState,true)); return store; } beforeEach(() => { act(() => storeResetFns.forEach(resetFn => resetFn())); }); 위의 __mocks__/zustand.js 에서 reset할 때 true를 사용하는 건 이해가 됩니다.그런데 아래의 mockZustandStore 에서 replace:true를 넣었는데 왜 넣어줬는지가 궁금합니다. const mockStore = (hook, state) => { const initStore = hook.getState(); hook.setState({ ...initStore, ...state }, true); }; export const mockUseUserStore = state => { mockStore(useUserStore, state); }; replace:true는 완전히 해당 store에 대한 상태,함수들을 교체 하는것으로 알고 있습니다.넣어준 의도 또는 이유가 있을까요?? 안 넣어주면 생기는 문제들이 있었기 때문에 replace:true로 한건지관례로 넣었던 건지
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
프로젝트 세팅 오류 및 버전 오류 문제 문의
안녕하세요 강의를 보며 프로젝트 세팅을 하면서 궁금한 점이 있어 여쭤봅니다.예제 프로젝트와 강의가 업데이트 될 예정은 없을까요?전체적인 Dependencie들이 옛날 버전이라 예제 프로젝트들에서 익스텐션 호환 및 에러들이 너무 많습니다 ㅠㅠ..1-3강 예시 프로젝트 소개 같은 경우는 ESLint 및 서버 오류들이 너무 많아 해결 못하고 넘어간 상황입니다.2-1강 같은 경우도 Windows/Mac OS 차이(LF, CRLF)로 발생하는 줄바꿈 문자 차이 에러가 나오고 영상에 나온 익스텐션에 경우 Vitest Version >= v1.4.0이라 vitest 및 vitest/ui 등 업데이트를 매번 해야됩니다. (강의는 v0.33.0)2-1까지만 수강하더라도 전체 강의 5%를 넘어 환불도 안되는 상황이라 매번 에러 찾고 수정하는 실습은 제외하고 테스팅 흐름만 파악하는 용도로 강의를 들으려고 합니다.업데이트 예정이 있다면 알려주시면 감사하겠습니다!
-
해결됨실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
toggleIsModalOpened 테스트 할때 궁금한 점이 있습니다.
제 생각에는 단순히 true가 됨을 테스트 할 것이 아닌 false => true => false와 같은 테스트 단계가 필요하다는 생각이 드는데 이런 연속적인 테스트는 어떤 식으로 구성하는 것이 좋은가요?
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
로딩/에러처리 검증은 어떻게 하는게 좋을까요?
안녕하세요. API 요청이 포함된 컴포넌트를 대상으로 단위/통합 테스트할때 요청 상태에 따라 로딩, 에러 UI가 적절하게 렌더링 되는지도 검증을 해야하는지 궁금합니다.해야한다면, API 호출하는 모든 테스트에서 로딩/에러 UI 검증을 하는게 일반적이고 맞는 건지, 아니면 제 경우에는 ErrorBoundary랑 Suspense를 다음처럼 합쳐서 에러/로딩 처리용 wrapper(?)를 만들었는데, 이런 wrapper 테스트를 따로 작성하는게 맞는건지.. 궁금합니다 ㅎㅎ..interface ComposedBoundaryProps extends ErrorBoundaryPropsWithRender { suspenseFallback: ReactNode; children: ReactNode; reset: () => void; } const ComposedBoundary = ({ suspenseFallback, children, fallbackRender, reset, }: ComposedBoundaryProps) => { return ( <ErrorBoundary onError={(error: unknown) => { // ... }} onReset={reset} fallbackRender={fallbackRender} > <Suspense fallback={suspenseFallback}>{children}</Suspense> </ErrorBoundary> ); };
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
통합 테스트에서 API 요청에 대한 검증은 이루어지지 않아도 괜찮을까요?
FE에서 모킹을 포함한 통합 테스트를 진행할 때, API 요청 전까지의 동작API 요청API 응답API 응답에 따른 화면 변환 1, 4는 테스트에서 주로 다루고 있고,3은 모킹으로 처리하고 있는데, 2번의 경우, 예를 들어 화면에서 필터로 { name: "apple", limit: 10 } 를 설정하고 요청이 발생했으면, request의 parameter에 이런 요청이 도달했다는 것 까지 검증하면 좋을지가 궁금합니다. 생성/삭제/수정과 같은 API가 포함된 경우 동일 GET API라도 다른 응답이 반환되어야 되는 경우가 있어, 해당 테스트 시 응답 자체를 override해서 모킹하여서 작성하였는데, 이 경우 사실상 API 응답과 API 응답에 따른 화면 변환은 테스트가 성공하는 채로 고정하는데, 실제 요청이 올바르게 이루어졌는지는 검증하지 못하는 것 같아서요.
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
lint에러..
await render(<TextField className={'my-class'} />); expect(screen.getByPlaceholderText('텍스트를 입력해 주세요.')).toHaveClass( 'my-class', );이렇게 props를 넘겨줄때 동일하게 작은 따옴표로 넘겨주거나 expect에 들어가는 문자열을 작은 따옴표로 입력을 해야 테스트전 린트에서 잡히는 에러가 안나네요;;
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 2부. 테스트 심화: 시각적 회귀・E2E 테스트
e2e 테스트 CI , 서버비용
안녕하세요 e2e 테스트는 비용이 많이 들어서 어떻게 관리를 하는지 궁금합니다. - 파이프라인에서 매번 CI 에서 돌리기에는 서버나, 시간이 개발 생산성을 잡아 먹을 것 같은데요. 점점 e2e 테스트가 쌓여 나갈떄 어떤 전략을 취할 수 있는지 궁금해요. GPT로 리서치하니까 보통 2가지를 병행해서 스케줄러 + 온디맨드 실행 같이 관리 한다고 하는데, 실제 현업에서는 어떻게 활용하고 계신지, 제가 리서치한 내용 말고 더 효율적이거나, 추천할만한 전략 , 이외의 고려사항 소개해주실 수 있으면 알려주시면 감사하겠습니다.스케줄 실행 (Nightly/주말 풀스윗)EventBridge → ECS Fargate RunTask(Spot 가능) → Playwright 러너대상: 스테이징(or PR 프리뷰 URL)b. 산출물: 트레이스/비디오/리포트 S3 업로드, Slack에 요약/링크온디맨드 실행 (PR 코멘트/수동 트리거)Bitbucket Pipeline에서 aws ecs run-task 호출(또는 ChatOps 슬래시 커맨드)태그/폴더/샤드 인자 넘김 → 필요한 부분만
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
단언문 순서에 따라 테스트 결과가 왜 달라지나요?
expect(screen.getByTestId('cart-icon')).toBeInTheDocument(); expect( await screen.findByRole('button', { name: 'Maria' }), ).toBeInTheDocument(); expect(screen.getByText('2')).toBeInTheDocument();영상의 테스트 코드는 통과하는데, 위와 같이 두번째와 세번째 단언문을 바꾸면 실패합니다. 왜이런가요?
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
useNavigate 테스트 시, 검증 대상 질문입니다.
뒤로 이동 버튼을 눌러 페이지 전환이 잘 되었음을 검증하고 싶으면 history web api 등을 이용해서 검증하는게 더 올바르지 않나요??왜 useNavigate 의 반환 함수의 인자로 검증하는지 궁금합니다~
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
강의 예시프로젝트 업데이트좀 부탁드립니다.
아니면 강의 설명 동영상에프로젝트 초기버전 세팅 등 각종 프로젝트 설정을 자력으로 해야한다. 라는 안내문구라도 존재하면 좋겠습니다. 문제 해결을 할수는 있지만 초기 세팅에 시간이 많이 드는건 사실입니다.
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
통합테스트와 단위테스트 파일 분리
통합 테스트와 단위 테스트를 작성할 때 따로 파일 구분 없이 작성하는게 일반적인가요?
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
grid 양옆에 margin은 어디서 설정되어있는건가요 ?
찾아보니 잘안보여서 네비게이션 밑에 필터부터 전체 마진 양옆으로 먹어진거 ...로우당 그리드 개수 설정이랑 ..
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
vitest 설치했는데
이렇게 발견된 테스트가 없다면서 8:39 화면처럼 테스트확장에 리스트가 출력되지않습니다.. 브랜치받고 npm ci 까지 한 상태입니다.커서 ide 참고로 0.2.42 버전 없어서 제일낮은 0.3 버전사용해도 못찾습니다 .. Vitest v0.33.0 is not supported. Vitest v1.4.0 or newer is required. 이렇게 출려되는데 버전을 올릴까요 ?
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
2부 할인쿠폰 관련
안녕하세요.제공해주신 인강으로 프론트엔드 테스트를 공부하고 있는 수강생입니다. 최근 일이 많아져 인강 듣는 시간을 할애하지 못하였지만 이번 설 연휴를 맞이하여 1편 다보고 2편까지 보려고 하는데 제가 실수로 할인 쿠폰을 발급 받았다가 사용하지 못하고 유효기간이 지나 버렸는데 재발급을 할수 있는 방법이 있는지 문의드립니다!
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
에러 해결 방법
[0] Failed running 'server/index.js' [1] [1] VITE v4.4.4 ready in 271 ms [1] [1] ➜ Local: http://localhost:5173/ [1] ➜ Network: use --host to expose [0] Restarting 'server/index.js' [0] file:///Users/kim-yongmin/test-example-shopping-mall/server/index.js:9 [0] import productsJSON from './response/products.json' assert { type: 'json' }; [0] ^^^^^^ [0] [0] SyntaxError: Unexpected identifier 'assert' [0] at compileSourceTextModule (node:internal/modules/esm/utils:337:16) [0] at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:164:18) [0] at callTranslator (node:internal/modules/esm/loader:439:14) [0] at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:445:30) [0] at async ModuleJob._link (node:internal/modules/esm/module_job:106:19) [0] [0] Node.js v22.5.1 [0] Failed running 'server/index.js' 3.1 강의 시청 후 test-example-shopping-mall 브랜치에서, 작업을 시작할려고, 서버와 프로젝트를 모두 킬려고 하는데 잘 동작하지 않습니다. 이에 대한 해결방법이 있을까요?
-
해결됨실무에 바로 적용하는 프런트엔드 테스트 - 2부. 테스트 심화: 시각적 회귀・E2E 테스트
Retry-ability와 커스텀 커맨드, 커스텀 쿼리 질문
안녕하세요 선생님 강의 잘 듣고 있습니다. 이번 강의에서 질문이 있습니다! 1. 커스텀 쿼리도 Retry-ability 지원되고 커스텀 쿼리 안에 커맨드도 Retry-ability가 지원되면 n의 m제곱 번의 재시도가 발생하는 것일까요? 2. 커스텀 커맨드와 커스텀 쿼리 중에 뭘 사용할 것인지는 Retry-ability 지원 유무와 체이닝을 기준으로 선택하면 될까요? 예시코드를 봤을 때 getCardButton와getProductCardByIndex 둘 다 DOM 요소를 조회해서 subject를 리턴하여 체이닝을 통해 후속 작업을 하는 것처럼 보이는데 왜 getProductCardByIndex는 커스텀 커맨드로 작성하는지 잘 모르겠습니다..
-
해결됨실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
직접 구현한 atom 컴포넌트 테스트 범위 질문
안녕하세요 선생님 강의 잘 듣고 있습니다.직접 구현한 atom 컴포넌트들의 테스트 범위에 대하여 질문드립니다.Typography, Badge, Divider와 같이 별도의 로직이 존재하지 않는 컴포넌트들은 ProductInfoArea처럼 스토리북으로 확인만 해도 되는 것인지 궁금합니다.아니면 atom 컴포넌트의 경우에는 props로 전달 받은 값이 잘 반영되는지 검증이 필요할까요? ( className 값이 잘 적용되는지, Typography의 경우 children 값이 화면에 잘 노출되는지 등등)
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 1부. 테스트 기초: 단위・통합 테스트
setup, teardown 동작 순서
안녕하세요!setup, teardown 강의를 보며 실습하고 있습니다.beforeAll 내에 console이 첫번째로 찍히다가 afterAll과 함께 사용할 경우에는 afterAll 바로 직전(마지막 바로 앞)에 찍히고 있습니다. (afterAll을 지울 경우에는 첫번째로 찍히고 있습니다.) beforeEach는 it을 실행하는 횟수만큼 실행되는거 같은데요. describe내에 선언한 beforeEach는 describe내에 호출한 it의 횟수만큼 실행되는게 맞는거 같은데 root의 beforeEach, afterEach도 it의 횟수만큼 실행되는게 맞을까요?제가 사용한 코드와 출력화면 입니다.import { screen } from '@testing-library/react'; import React from 'react'; import TextField from '@/components/TextField'; import render from '@/utils/test/render'; beforeEach(() => { console.log('2. root - beforeEach'); }); beforeAll(() => { console.log('1. root - beforeAll'); }); afterEach(() => { console.log('5. root - afterEach'); }); afterAll(() => { console.log('6. root - afterAll'); }); describe('placeholder', () => { beforeEach(() => { console.log('3. placeholder - beforeEach'); }); afterEach(() => { console.log('4. placeholder - afterEach'); }); it('기본 placeholder "텍스트를 입력해 주세요."가 노출된다.', async () => { await render(<TextField />); const textInput = screen.getByPlaceholderText('텍스트를 입력해 주세요.'); expect(textInput).toBeInTheDocument(); }); it('placeholder prop에 따라 placeholder가 변경된다.', async () => { await render(<TextField placeholder="상품명을 입력해 주세요." />); const textInput = screen.getByPlaceholderText('상품명을 입력해 주세요.'); expect(textInput).toBeInTheDocument(); }); }); /** 실행 결과 2. root - beforeEach 3. placeholder - beforeEach 4. placeholder - afterEach 5. root - afterEach 2. root - beforeEach 3. placeholder - beforeEach 4. placeholder - afterEach 5. root - afterEach 1. root - beforeAll 6. root - afterAll */