Skip to main content

testing

Unit/integration testing standards for RedisInsight using Jest and Testing Library: test structure, the `renderComponent` helper, faker for test data, mocking patterns, and `waitFor` instead of fixed time waits. Use when writing or modifying any `*.spec.ts` or `*.spec.tsx` file, when adding component or slice tests, when debugging flaky tests, or when the user mentions jest, testing library, faker, or test patterns.

소스 정보

저장소
redis/RedisInsight
최근 소스 활동
2026년 6월 22일 11:41
감지된 SKILL.md 언어
영어
스타
8,906
포크
489

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
testing
description
Unit/integration testing standards for RedisInsight using Jest and Testing Library: test structure, the `renderComponent` helper, faker for test data, mocking patterns, and `waitFor` instead of fixed time waits. Use when writing or modifying any `*.spec.ts` or `*.spec.tsx` file, when adding component or slice tests, when debugging flaky tests, or when the user mentions jest, testing library, faker, or test patterns.
# Testing Standards and Practices ## Core Principles - **Write tests for all new features** - Follow **AAA pattern**: Arrange, Act, Assert - Use **descriptive test names**: "should do X when Y" - **CRITICAL**: Never use fixed time waits - tests must be deterministic - **CRITICAL**: Use faker library (@faker-js/faker) for test data ## Test Organization ```typescript describe('FeatureService', () => { describe('findById', () => { it('should return entity when found', () => {}); it('should throw NotFoundException when not found', () => {}); }); describe('create', () => { it('should create entity with valid data', () => {}); it('should throw error with invalid data', () => {}); }); }); ``` ## Frontend Testing (Jest + Testing Library) ### Running Specific Tests ```bash # Run a specific test file node 'node_modules/.bin/jest' 'redisinsight/ui/src/path/to/Component.spec.tsx' -c 'jest.config.cjs' # Run a specific test by name (use -t flag) node 'node_modules/.bin/jest' 'redisinsight/ui/src/path/to/Component.spec.tsx' -c 'jest.config.cjs' -t 'test name pattern' # Example: node 'node_modules/.bin/jest' 'redisinsight/ui/src/slices/tests/browser/keys.spec.ts' -c 'jest.config.cjs' -t 'refreshKeyInfoAction' ``` ### CRITICAL: Always Use Shared `renderComponent` Helper **Create a `renderComponent` helper for each component test file**: ```typescript import { faker } from '@faker-js/faker'; // Pull structured data from a shared factory (redisinsight/ui/src/mocks/factories/); // only build ad-hoc primitives/callbacks inline. describe('MyComponent', () => { // Default props: factory for the entity, inline only for callbacks/primitives const mockUser = UserFactory.build(); const defaultProps: MyComponentProps = { user: mockUser, onComplete: jest.fn(), }; // Shared render helper const renderComponent = (propsOverride?: Partial<MyComponentProps>) => { const props = { ...defaultProps, ...propsOverride }; return render( <Provider store={store}> <MyComponent {...props} /> </Provider> ); }; beforeEach(() => { jest.clearAllMocks(); }); it('should render component', () => { renderComponent(); expect(screen.getByText(defaultProps.name)).toBeInTheDocument(); }); it('should handle click', async () => { const mockOnComplete = jest.fn(); renderComponent({ onComplete: mockOnComplete }); fireEvent.click(screen.getByRole('button')); await waitFor(() => { expect(mockOnComplete).toHaveBeenCalledTimes(1); }); }); }); ``` **Benefits**: - Centralized setup (providers, router, theme) - Default props defined once - Easy prop overrides per test - No duplicate setup code ### Complex Component Setup For components requiring Router, ThemeProvider, etc., include them in `renderComponent`: ```typescript const renderComponent = (propsOverride?: Partial<Props>) => { const props = { ...defaultProps, ...propsOverride } return render( <Provider store={store}> <BrowserRouter> <ThemeProvider theme={theme}> <Component {...props} /> </ThemeProvider> </BrowserRouter> </Provider> ) } ``` ### Testing with Redux Create a test store with `configureStore` for Redux-connected components: ```typescript const createTestStore = (initialState = {}) => { return configureStore({ reducer: { user: userSlice.reducer }, preloadedState: initialState, }); }; const renderComponent = (propsOverride?: Partial<Props>, storeState = {}) => { const testStore = createTestStore(storeState); // render with testStore }; ``` ### Query Priorities (Testing Library) **Prefer accessible queries** (as users would interact): ```typescript // ✅ PREFERRED screen.getByRole('button', { name: /submit/i }); screen.getByLabelText('Email'); screen.getByPlaceholderText('Enter name'); // ⚠️ LAST RESORT screen.getByTestId('user-profile'); // ❌ AVOID wrapper.find('.button-class'); ``` ### Testing Async Behavior ```typescript // ✅ GOOD: waitFor with proper queries await waitFor(() => { expect(screen.getByText('Data loaded')).toBeInTheDocument(); }); // ✅ GOOD: waitForElementToBeRemoved await waitForElementToBeRemoved(() => screen.queryByText('Loading...')); // ✅ GOOD: findBy queries (built-in waiting) const element = await screen.findByText('Async content'); // ❌ BAD: Fixed timeouts (flaky tests) await new Promise((resolve) => setTimeout(resolve, 1000)); ``` ### Mocking API Calls (MSW) Use Mock Service Worker for API mocking: ```typescript import { rest } from 'msw'; import { setupServer } from 'msw/node'; const server = setupServer( rest.get('/api/users/:id', (req, res, ctx) => { return res( ctx.json({ id: req.params.id, name: faker.person.fullName(), }), ); }), ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` ## Backend Testing (NestJS/Jest) ### Service Test Pattern ```typescript import { Factory } from 'fishery'; import { faker } from '@faker-js/faker'; // Define factory for User entity const userFactory = Factory.define<User>(() => ({ id: faker.string.uuid(), name: faker.person.fullName(), email: faker.internet.email(), })); describe('UserService', () => { let service: UserService; let repository: Repository<User>; const mockRepository = { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), update: jest.fn(), delete: jest.fn(), }; beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ UserService, { provide: getRepositoryToken(User), useValue: mockRepository, }, ], }).compile(); service = module.get<UserService>(UserService); repository = module.get<Repository<User>>(getRepositoryToken(User)); }); afterEach(() => { jest.clearAllMocks(); }); it('should return user when found', async () => { const mockUser = userFactory.build(); mockRepository.findOne.mockResolvedValue(mockUser); const result = await service.findById(mockUser.id); expect(result).toEqual(mockUser); }); }); ``` ### Controller Test Pattern ```typescript import { Factory } from 'fishery'; import { faker } from '@faker-js/faker'; const userFactory = Factory.define<User>(() => ({ id: faker.string.uuid(), name: faker.person.fullName(), email: faker.internet.email(), })); describe('UserController', () => { let controller: UserController; let service: UserService; const mockService = { findAll: jest.fn(), findById: jest.fn(), create: jest.fn(), }; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [UserController], providers: [{ provide: UserService, useValue: mockService }], }).compile(); controller = module.get<UserController>(UserController); }); it('should return user from service', async () => { const mockUser = userFactory.build(); mockService.findById.mockResolvedValue(mockUser); const result = await controller.findById(mockUser.id); expect(result).toEqual(mockUser); }); }); ``` ### Integration Tests (E2E) ```typescript describe('UserController (e2e)', () => { let app: INestApplication; beforeAll(async () => { const module = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = module.createNestApplication(); await app.init(); }); afterAll(async () => { await app.close(); }); it('/users (GET)', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); }); }); }); ``` ## E2E Testing (Playwright) ```typescript import { Factory } from 'fishery'; import { faker } from '@faker-js/faker'; const userDataFactory = Factory.define(() => ({ name: faker.person.fullName(), email: faker.internet.email(), })); test.describe('User Management', () => { test('should create new user', async ({ page }) => { const userData = userDataFactory.build(); await page.goto('/users'); await page.click('text=Add User'); await page.fill('[name="name"]', userData.name); await page.fill('[name="email"]', userData.email); await page.click('text=Submit'); // ✅ Use proper waits await expect(page.locator(`text=${userData.name}`)).toBeVisible(); }); }); ``` ## Best Practices ### Always Use Faker for Test Data ```typescript // ✅ GOOD: Use faker const user = { id: faker.string.uuid(), name: faker.person.fullName(), email: faker.internet.email(), age: faker.number.int({ min: 18, max: 100 }), }; // ❌ BAD: Hardcoded data const user = { id: '123', name: 'Test User' }; ``` ### Use Factories Instead of Static Mocks **Use Fishery** for creating test data factories with sensible defaults and overrides. **CRITICAL — UI factories live in `redisinsight/ui/src/mocks/factories/`**, organized by domain (`<domain>/<TypeName>.factory.ts`), exported as `<TypeName>Factory`. Before writing test data in a UI spec, **check for an existing factory there and reuse it**; if none fits, **add a new factory file in that directory** rather than inlining mocks in the spec. ```typescript // ✅ GOOD: reuse the shared factory (redisinsight/ui/src/mocks/factories/database/DBInstance.factory.ts) import { DBInstanceFactory } from 'uiSrc/mocks/factories/database/DBInstance.factory'; const instance = DBInstanceFactory.build(); const cluster = DBInstanceFactory.build({ connectionType: ConnectionType.Cluster, }); const many = DBInstanceFactory.buildList(5); // ✅ GOOD: a new factory file, when none exists yet
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기