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
آخر نشاط في المصدر
٢٢ يونيو ٢٠٢٦ في ١١:٤١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٨٬٩١٧
التفرعات
٤٨٩

خيارات التثبيت

يُحدَّد 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