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.
69
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
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', () => {});
});
});# 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'renderComponent HelperCreate a renderComponent helper for each component test file:
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:
For components requiring Router, ThemeProvider, etc., include them in renderComponent:
const renderComponent = (propsOverride?: Partial<Props>) => {
const props = { ...defaultProps, ...propsOverride }
return render(
<Provider store={store}>
<BrowserRouter>
<ThemeProvider theme={theme}>
<Component {...props} />
</ThemeProvider>
</BrowserRouter>
</Provider>
)
}Create a test store with configureStore for Redux-connected components:
const createTestStore = (initialState = {}) => {
return configureStore({
reducer: { user: userSlice.reducer },
preloadedState: initialState,
});
};
const renderComponent = (propsOverride?: Partial<Props>, storeState = {}) => {
const testStore = createTestStore(storeState);
// render with testStore
};Prefer accessible queries (as users would interact):
// ✅ 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');// ✅ 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));Use Mock Service Worker for API mocking:
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());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);
});
});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);
});
});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);
});
});
});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();
});
});// ✅ 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 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.
// ✅ 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
// redisinsight/ui/src/mocks/factories/<domain>/<TypeName>.factory.ts
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
const userFactory = Factory.define<User>(({ sequence }) => ({
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
age: faker.number.int({ min: 18, max: 100 }),
}));
// Usage - flexible and reusable
const user1 = userFactory.build();
const user2 = userFactory.build({ age: 25 });
const user3 = userFactory.build({ name: 'Specific Name' });
const users = userFactory.buildList(5); // Create multiple
// ❌ BAD: Static mock objects
const mockUser1 = {
id: '123',
name: 'User 1',
email: 'user1@test.com',
age: 30,
};Benefits of Fishery factories:
// ❌ BAD: Fixed timeout
await new Promise((resolve) => setTimeout(resolve, 1000));
await page.waitForTimeout(2000);
// ✅ GOOD: Wait for condition
await waitFor(() => {
expect(element).toBeInTheDocument();
});
await page.waitForSelector('[data-test="result"]');// ✅ GOOD: Mock services
jest.mock('uiSrc/services/api', () => ({
apiService: {
get: jest.fn(),
post: jest.fn(),
},
}));it.eachUse it.each for multiple tests with the same body but different inputs:
// ✅ GOOD: Parameterized tests
it.each([
{ description: 'null', value: null },
{ description: 'undefined', value: undefined },
{ description: 'empty string', value: '' },
{ description: 'whitespace only', value: ' ' },
])('should return error when input is $description', async ({ value }) => {
const result = await service.processInput(value);
expect(result.status).toBe('error');
});Benefits:
Always test:
renderComponent helper used6029714
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.