| name | unit-testing |
| description | Guides writing and maintaining Vitest unit tests for the bootc extension's backend, frontend, and shared packages. Covers mocking @podman-desktop/api, RPC clients, Svelte stores, and Node.js modules. Triggers when creating or modifying .spec.ts files, fixing Vitest failures, or adding test coverage. |
| paths | ["packages/backend/src/**/*.spec.ts","packages/frontend/src/**/*.spec.ts","packages/shared/src/**/*.spec.ts"] |
| argument-hint | [test-file-or-pattern] |
Unit Testing for Bootc Extension
Framework
- Vitest 4 with v8 coverage provider
- Backend: Node.js environment, mocked
@podman-desktop/api
- Frontend: JSDOM environment,
@testing-library/svelte for components
- Shared: Node.js environment for message proxy and model tests
Running Tests
pnpm test
pnpm test:backend
pnpm test:frontend
pnpm test:shared
npx vitest run packages/backend/src/build-disk-image.spec.ts
npx vitest watch packages/backend/src/
Backend Test Patterns
Mocking @podman-desktop/api
The backend's vite.config.js aliases @podman-desktop/api to a mock file.
Mock only the namespaces your test actually uses:
import { vi, test, expect, beforeEach, describe } from 'vitest';
import * as extensionApi from '@podman-desktop/api';
vi.mock('@podman-desktop/api', async () => ({
}));
To see the full mock shape used in existing tests:
grep -A 30 "vi.mock('@podman-desktop/api'" packages/backend/src/*.spec.ts | head -60
Testing Backend Functions
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { buildDiskImage } from './build-disk-image';
vi.mock('@podman-desktop/api');
vi.mock('./container-utils');
vi.mock('./history');
beforeEach(() => {
vi.clearAllMocks();
});
describe('buildDiskImage', () => {
test('should create build container with correct volumes', async () => {
const buildInfo = {
id: 'test-id',
image: 'quay.io/test/image',
tag: 'latest',
type: ['qcow2'] as BuildType[],
folder: '/output',
};
await buildDiskImage(buildInfo);
expect(containerEngine.createContainer).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
Image: expect.stringContaining('bootc-image-builder'),
}),
);
});
});
Mocking Node.js Modules
import { vi } from 'vitest';
vi.mock('node:fs', async () => ({
promises: {
mkdir: vi.fn(),
readFile: vi.fn(),
writeFile: vi.fn(),
},
existsSync: vi.fn(),
}));
vi.mock('node:child_process', async () => ({
exec: vi.fn(),
}));
Frontend Test Patterns
Component Testing with @testing-library/svelte
import { vi, test, expect, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
import '@testing-library/jest-dom/vitest';
import MyComponent from './MyComponent.svelte';
vi.mock('/@/api/client', async () => ({
bootcClient: {
listBootcImages: vi.fn().mockResolvedValue([]),
buildImage: vi.fn(),
},
rpcBrowser: {
subscribe: vi.fn().mockReturnValue({ unsubscribe: vi.fn() }),
},
}));
test('renders heading', async () => {
render(MyComponent, { props: { imageName: 'test' } });
expect(screen.getByText('Build Disk Image')).toBeInTheDocument();
});
test('clicking build button triggers build', async () => {
render(MyComponent);
const buildButton = screen.getByRole('button', { name: 'Build' });
await fireEvent.click(buildButton);
expect(bootcClient.buildImage).toHaveBeenCalled();
});
Mocking Svelte Stores
import { vi } from 'vitest';
import { readable } from 'svelte/store';
vi.mock('/@/stores/imageInfo', async () => ({
imageInfos: readable([{ Id: 'abc123', RepoTags: ['quay.io/test:latest'], Labels: { 'containers.bootc': '' } }]),
}));
Path Aliases in Tests
Frontend tests resolve /@/ to packages/frontend/src/ and /@shared/ to
packages/shared/ via Vite config. Use these aliases in mocks:
vi.mock('/@/api/client');
vi.mock('/@shared/src/models/bootc');
Shared Package Tests
MessageProxy Tests
import { test, expect, vi } from 'vitest';
import { RpcBrowser, RpcExtension } from './MessageProxy';
test('RPC call resolves with result', async () => {
});
Test File Naming
- Test files:
*.spec.ts colocated with source files
- Example:
build-disk-image.ts → build-disk-image.spec.ts
Coverage
Coverage reports are generated in lcov and text format:
pnpm test:backend
pnpm test:frontend
Common Issues
"Cannot find module @podman-desktop/api"
The mock alias is configured in packages/backend/vite.config.js. If you see
this error, ensure the test file is being run from the correct package.
"Not a Svelte component"
The test environment needs the Svelte vite plugin. Frontend tests use the
config from packages/frontend/vite.config.js which includes @sveltejs/vite-plugin-svelte.
Test Retries
Frontend tests retry failed tests 2x (configured in packages/frontend/vite.config.js).
Backend tests do not retry.