Write tests for the akash-network/console monorepo following established team patterns and reviewer expectations. Use this skill whenever you need to write, fix, review, or refactor tests in the console project — including unit tests, functional tests, integration tests, or E2E tests for both frontend (deploy-web) and backend (api, notifications, indexer, provider-proxy). Also trigger when the user mentions 'write tests', 'add tests', 'fix tests', 'test this', 'spec file', or asks about testing patterns in the console codebase. When in doubt about whether to use this skill for a testing task in this repo, USE IT.
92
—
Does it follow best practices?
Impact
88%
1.76xAverage score across 2 eval scenarios
Passed
No known issues
Testing conventions and patterns for the akash-network/console monorepo.
Choose the lowest-effective test level:
| Level | When to use in this repo | Mocking strategy |
|---|---|---|
| Unit (default) | Components, hooks, services, utilities | All deps mocked via DI (mock<T>()) — never vi.mock() |
| Integration | Services with heavy DB logic or Repository patterns | Real DB fixtures; mock only 3rd-party calls |
| Functional | Black-box HTTP endpoint verification | nock for external calls only; never mock internal services |
| E2E | Post-deployment verification (beta/prod) | No mocks; happy path only; Playwright with semantic locators |
Functional tests should only fail when functional requirements change, not from refactoring. Don't write functional tests for simple routes — test the service layer directly.
setup Function PatternThis is the most important convention. Never use beforeEach with shared mutable variables.
setup() function at the bottom of the root describe blocksetup() independently — no shared mutable state across testsdescribe(BalancesService.name, () => {
it("returns balances for a given address", async () => {
const { service, balanceRepository } = setup();
balanceRepository.findByAddress.mockResolvedValue([{ denom: "uakt", amount: "1000" }]);
const result = await service.getBalances("akash1abc...");
expect(result).toEqual([{ denom: "uakt", amount: "1000" }]);
});
it("returns empty array when no balances found", async () => {
const { service, balanceRepository } = setup();
balanceRepository.findByAddress.mockResolvedValue([]);
const result = await service.getBalances("akash1xyz...");
expect(result).toEqual([]);
});
function setup(input?: { customConfig?: Partial<BalancesConfig> }) {
const balanceRepository = mock<BalanceRepository>();
const config = mockConfigService<BalancesConfig>({ ...input?.customConfig });
const service = new BalancesService(balanceRepository, config);
return { service, balanceRepository, config };
}
});Skip setup only if creating the object under test is trivially simple (e.g., testing a pure function with no dependencies).
Root describe: Use SubjectUnderTest.name (the actual reference, not a string). This enables IDE refactoring and reference-finding.
// Good
describe(StripeWebhookService.name, () => { ... });
// Bad
describe("StripeWebhookService", () => { ... });Nested describe: Use either a method name or a "when ..." condition.
describe(StripeWebhookService.name, () => {
describe("handleChargeRefunded", () => { ... });
describe("when payment method is missing", () => { ... });
});it blocks: Use present simple, 3rd person singular. Do NOT prepend with "should".
// Good
it("returns early when customer ID is missing", async () => { ... });
it("updates both limits and isTrialing when endTrial is true", async () => { ... });
// Bad
it("should return early when customer ID is missing", async () => { ... });mock<T>() from vitest-mock-extendedAlways use mock<T>() for typed mocks. Never use jest.mock() or vi.mock() for module-level mocking — it causes OOM with heavy component trees and couples tests to implementation details.
import { mock } from "vitest-mock-extended";
const userRepository = mock<UserRepository>();
const stripeService = mock<StripeService>();
const logger = mock<LoggerService>();Prefer mock<T>() over as unknown as <Type> for hand-built doubles. When stubbing a hook return value or service that has many fields, build it with mock<T>() and override only the fields the test cares about. Hand-built objects coerced through as unknown as silently survive contract changes upstream and hide real regressions.
// Good — stays in sync with the real type, lets the test focus on what matters
const useCustomUser: typeof DEPENDENCIES.useCustomUser = () =>
mock<ReturnType<typeof DEPENDENCIES.useCustomUser>>({
user: { username: "alice" },
isLoading: false
});
// Bad — `UseCustomUser` could grow a required field and this still compiles
const useCustomUser = () =>
({ user: { username: "alice" }, isLoading: false }) as unknown as ReturnType<typeof DEPENDENCIES.useCustomUser>;Reserve as unknown as for the rare case where the type is structurally incompatible (e.g. branded types) and mock<T>() cannot satisfy it.
For config services, use mockConfigService<T>() (in apps/api/test/mocks/config-service.mock.ts):
const billingConfig = mockConfigService<BillingConfigService>({
DEPLOYMENT_GRANT_DENOM: "uakt",
TRIAL_ALLOWANCE_EXPIRATION_DAYS: 14
});Always explicitly import from vitest:
import { describe, expect, it, vi } from "vitest";Explicit imports make dependencies visible and simplify TypeScript types.
if Statements in AssertionsNever use conditional logic in test expectations. If you need to narrow a type, use as assertions. Otherwise, skipped assertions silently pass when they shouldn't.
// Good
const result = await service.validate(cert) as CertValidationResultError;
expect(result.ok).toBe(false);
expect(result.code).toBe("unknownCertificate");
// Bad
const result = await service.validate(cert);
if (!result.ok) {
expect(result.code).toBe("unknownCertificate");
}Follow the AAA pattern. Setup prepares state; the test validates it. Don't mix concerns.
it("creates a deployment grant", async () => {
// Arrange
const { service, grantService } = setup();
grantService.create.mockResolvedValue(mockGrant);
// Act
const result = await service.createGrant("akash1abc...");
// Assert
expect(result).toEqual(mockGrant);
expect(grantService.create).toHaveBeenCalledWith("akash1abc...");
});If a service catches and transforms errors, test those paths. Don't skip error coverage just because the happy path works — and flag unhandled error scenarios rather than ignoring them.
Read @references/frontend-patterns.md for the full set of frontend-specific patterns including the DEPENDENCIES pattern, hook testing, query testing, and container testing.
Key points:
getBy* for presence assertions (toBeInTheDocument()), and queryBy* for absence assertions (not.toBeInTheDocument())DEPENDENCIES export + dependencies prop for component DI (never vi.mock) — this covers components, hooks, and any other heavy importsMockComponents() helper to auto-mock dependenciesuseServices hook, not via DEPENDENCIES propsetupQuery() utility for React Query hook testsrenderHook from @testing-library/react for hook testsRead @references/api-patterns.md for the full set of API-specific patterns including service testing, config mocking, and seeder patterns.
Key points:
mock<T>() dependenciesapps/api/test/seeders/)@faker-js/faker for randomized data in seedersPartial<T> overrides with sensible defaultsRead @references/api-patterns.md for functional test setup details.
Key points:
TestDatabaseService (auto-created, migrated, dropped)nock, not internal servicesfetch()-based helpers) for Hono apps, supertest for NestJS — avoid framework-internal methods like app.request()@nestjs/testing Test.createTestingModule() for DIMockProvider() helper to create NestJS providers with vitest-mock-extended mockssupertest with a real NestJS app and per-file test databasesgetByRole, getByLabel, getByPlaceholder — never CSS selectors or data-testidgetByRole('button', { name: /Submit/ }) — don't add redundant aria-label to buttons that already have textwaitFor(...) instead of setTimeout — timeouts cause flakinessconsole.log or manual screenshots — use Playwright's built-in traces (npx playwright --ui)Promise.all with context.waitForEvent("page") for navigation patternsmy-service.ts → my-service.spec.ts*.spec.tsx for components, *.spec.ts for logicsrc/**/*.spec.tssrc/**/*.integration.tstest/functional/**/*.spec.tstest/e2e/**/*.spec.tstest/seeders/ in each appVerify before committing:
npm test (or npm run test:unit / npm run test:functional depending on scope)npx tsc --noEmit in the app directorynpm run lint -- --quietexpect(true).toBe(true))7b6a52e
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.