CtrlK
BlogDocsLog inGet started
Tessl Logo

console-tests

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.

90

1.81x
Quality

86%

Does it follow best practices?

Impact

98%

1.81x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No known issues

SKILL.md
Quality
Evals
Security

Console Test Writing Guide

This skill encodes the testing conventions and patterns for the akash-network/console monorepo.

Before writing any test, read the source file you're testing thoroughly. Understand what it does, what dependencies it has, and what level of testing is appropriate.

Deciding What Type of Test to Write

Choose the lowest-effective test level. Writing E2E tests for logic that can be unit tested wastes everyone's time and creates brittle, slow suites.

Unit tests (99% of cases):

  • Components, hooks, pure logic services, utilities
  • All dependencies mocked via DI (never module-level mocking)
  • Run at PR level, must be fast

Integration tests (service + database):

  • Services that rely heavily on database logic or Repository patterns
  • Use real database fixtures, not mocks
  • Mock only 3rd-party service calls if needed
  • Good for verifying queries, transactions, and data integrity

Functional / API tests (black-box HTTP):

  • Test the service as a black box through its HTTP endpoints
  • External network calls MUST be mocked with nock (v14+ supports native fetch in addition to http/https)
  • Do NOT mock internal application services — they're implementation details at this level
  • Should only fail when functional requirements change, not from refactoring
  • Don't write functional tests for simple routes — test the service layer directly instead

E2E tests (post-deployment verification):

  • Only for verifying deployed services in target environments (beta/prod)
  • No mocks at all
  • Happy path only
  • Use Playwright with semantic locators

Universal Rules (All Test Types)

The setup Function Pattern

This is the most important convention. Never use beforeEach with shared mutable variables.

  • Define a setup() function at the bottom of the root describe block
  • It constructs the unit under test and returns it along with all mocked dependencies
  • It accepts a single parameter with an inline type definition
  • Do not specify the return type — let TypeScript infer it
  • Each test calls setup() independently — no shared mutable state across tests
describe(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).

Test Description Conventions

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 () => { ... });

Mocking: mock<T>() from vitest-mock-extended

Always 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
});

Vitest Imports

Always explicitly import from vitest:

import { describe, expect, it, vi } from "vitest";

Explicit imports make dependencies visible and simplify TypeScript types.

No if Statements in Assertions

Never 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");
}

Arrange-Act-Assert (AAA)

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...");
});

Comments Answer WHY, Not WHAT

Remove obvious comments. If a comment just restates the method name or assertion, delete it.

Test Error Handling Thoughtfully

When testing error paths, focus on errors the code explicitly handles — but don't skip error coverage just because the happy path works. If a service catches and transforms errors, test those paths. If important error scenarios aren't handled in production code, that may be a gap worth flagging rather than ignoring.

Frontend Tests (deploy-web)

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:

  • Use getBy* for presence assertions (toBeInTheDocument()), and queryBy* for absence assertions (not.toBeInTheDocument())
  • Use the DEPENDENCIES export + dependencies prop for component DI (never vi.mock) — this covers components, hooks, and any other heavy imports
  • Use MockComponents() helper to auto-mock dependencies
  • Services are injected via useServices hook, not via DEPENDENCIES prop
  • Use setupQuery() utility for React Query hook tests
  • Use renderHook from @testing-library/react for hook tests

API Unit Tests

Read @references/api-patterns.md for the full set of API-specific patterns including service testing, config mocking, and seeder patterns.

Key points:

  • Construct services manually with mock<T>() dependencies
  • Use seeders for test data (apps/api/test/seeders/)
  • Use function-based seeders (not class-based)
  • Use @faker-js/faker for randomized data in seeders
  • Seeder accepts Partial<T> overrides with sensible defaults

API Functional Tests

Read @references/api-patterns.md for functional test setup details.

Key points:

  • Whitebox seeding, blackbox testing: Seed data at the DB/repository level, but interact only through HTTP
  • Each spec file gets its own database via TestDatabaseService (auto-created, migrated, dropped)
  • Don't resolve controllers/services from the DI container — don't call application services or other endpoints to set up state
  • Mock external HTTP calls with nock, not internal services
  • Use a plain HTTP client (fetch()-based helpers) for Hono apps, supertest for NestJS — avoid framework-internal methods like app.request()
  • Each test verifies a single endpoint's behavior in isolation
  • Use function-based seeders to create test fixtures in the real database
  • Write race condition tests for upsert operations

Notifications Tests (NestJS)

  • Use @nestjs/testing Test.createTestingModule() for DI
  • Use MockProvider() helper to create NestJS providers with vitest-mock-extended mocks
  • Functional tests use supertest with a real NestJS app and per-file test databases

E2E / Playwright Tests

  • Use semantic locators: getByRole, getByLabel, getByPlaceholder — never CSS selectors or data-testid
  • getByRole('button', { name: /Submit/ }) — don't add redundant aria-label to buttons that already have text
  • Page Objects abstract UI interactions but must NOT contain assertions
  • Use waitFor(...) instead of setTimeout — timeouts cause flakiness
  • No console.log or manual screenshots — use Playwright's built-in traces (npx playwright --ui)
  • No randomness in test data — it causes flaky failures
  • Use Promise.all with context.waitForEvent("page") for navigation patterns

Test File Organization

  • Test files are co-located with source: my-service.tsmy-service.spec.ts
  • Frontend: *.spec.tsx for components, *.spec.ts for logic
  • API unit: src/**/*.spec.ts
  • API integration: src/**/*.integration.ts
  • API functional: test/functional/**/*.spec.ts
  • API e2e: test/e2e/**/*.spec.ts
  • Seeders: test/seeders/ in each app
Repository
akash-network/console
Last updated
Created

Is this your skill?

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.