plugins/midnight-dapp/skills/testing-patterns/SKILL.md
Use when writing unit tests for Midnight contract interaction code, integration testing without ZK proofs, E2E testing with Playwright or Cypress, or setting up CI/CD pipelines for Midnight DApps.
npx skillsauth add aaronbassett/midnight-knowledgebase midnight-dapp:testing-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Test Midnight DApps efficiently using mocked providers, simulated wallets, and testnet integration strategies.
Midnight DApps face unique testing challenges:
| Challenge | Why It Matters | Solution | |-----------|----------------|----------| | Proof generation takes seconds | Tests would be too slow | Mock proof providers | | Wallet requires browser extension | Can't run in CI/CD | Mock wallet provider | | Private state is local only | Hard to verify in tests | Controlled test state | | Testnet requires real infrastructure | Flaky in automation | Mock for unit tests, testnet for E2E |
E2E (Testnet)
/ \
/ Real proofs \
/ Real wallet \
/ Slow (~min) \
/____________________\
|
Integration (Mocked)
/ \
/ Mock proof provider \
/ Mock wallet provider \
/ Fast (~seconds) \
/______________________________\
|
Unit Tests
/ \
/ Pure logic \
/ No providers \
/ Fast (~ms) \
/____________________\
| Test Type | Proof Provider | Wallet Provider | Use Case | |-----------|---------------|-----------------|----------| | Unit | N/A | N/A | Pure business logic | | Component | Mock | Mock | UI components | | Integration | Mock | Mock | Contract interactions | | E2E (local) | Mock | Mock | Full user flows | | E2E (testnet) | Real | Real (Lace) | Pre-deployment validation |
| Document | Description | |----------|-------------| | mocking-proofs.md | Mock proof provider for fast tests | | mock-wallet-provider.md | Simulating Lace wallet in tests | | testnet-workflows.md | E2E testing against testnet | | web3-comparison.md | Hardhat/Foundry testing vs Midnight |
| Example | Description | |---------|-------------| | mock-proof-context/ | Mock proof provider and test utilities | | mock-wallet/ | Fake wallet implementation for tests | | e2e-testnet/ | Playwright E2E test against testnet |
pnpm add -D vitest @testing-library/react @playwright/test msw
import { createMockProofProvider } from "./mockProofProvider";
// Returns dummy proofs instantly (no ZK computation)
const mockProofProvider = createMockProofProvider({
latencyMs: 10, // Simulate realistic timing
});
import { MockWallet } from "./MockWallet";
const mockWallet = new MockWallet({
address: "addr_test1qz_mock_address_for_testing_purposes_xyz",
balance: 1000000n,
network: "testnet",
});
// Inject into window for components that check window.midnight
globalThis.window = {
midnight: { mnLace: mockWallet.connector },
};
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { MockWallet } from "./MockWallet";
import { createMockProofProvider } from "./mockProofProvider";
import { TransferButton } from "../TransferButton";
describe("TransferButton", () => {
let mockWallet: MockWallet;
let mockProofProvider: MockProofProvider;
beforeEach(() => {
mockWallet = new MockWallet({ balance: 1000n });
mockProofProvider = createMockProofProvider();
});
it("should complete transfer with mocked providers", async () => {
render(
<TransferButton
wallet={mockWallet.api}
proofProvider={mockProofProvider}
recipient="addr_test1..."
amount={100n}
/>
);
fireEvent.click(screen.getByText("Transfer"));
// No actual proof generation - instant!
await screen.findByText("Transfer Complete");
expect(mockWallet.getBalance()).toBe(900n);
});
});
import { describe, it, expect } from "vitest";
import { createMockContract } from "./testUtils";
describe("Contract State", () => {
it("should read balance from contract state", async () => {
const contract = createMockContract({
state: {
balances: new Map([["addr_test1...", 500n]]),
totalSupply: 10000n,
},
});
const balance = await contract.state.balances.get("addr_test1...");
expect(balance).toBe(500n);
});
});
import { describe, it, expect } from "vitest";
import { witnesses, createInitialPrivateState } from "../witnesses";
describe("Witnesses", () => {
it("should return balance from private state", () => {
const privateState = createInitialPrivateState(new Uint8Array(32));
privateState.balance = 1000n;
const context = { privateState, setPrivateState: () => {} };
const balance = witnesses.get_balance(context);
expect(balance).toBe(1000n);
});
it("should throw for expired credential", () => {
const privateState = createInitialPrivateState(new Uint8Array(32));
privateState.credentials.set("abc123", {
expiry: BigInt(Date.now() / 1000 - 3600), // Expired 1 hour ago
data: new Uint8Array(32),
});
const context = { privateState, setPrivateState: () => {} };
expect(() =>
witnesses.get_credential(context, hexToBytes("abc123"))
).toThrow("expired");
});
});
import { describe, it, expect, vi } from "vitest";
import { MockWallet } from "./MockWallet";
describe("Error Handling", () => {
it("should handle user rejection", async () => {
const wallet = new MockWallet();
wallet.rejectNextTransaction("User rejected");
await expect(
wallet.api.submitTransaction(mockTx)
).rejects.toThrow("User rejected");
});
it("should handle proof server unavailable", async () => {
const proofProvider = createMockProofProvider({
shouldFail: true,
errorMessage: "Proof server unavailable",
});
await expect(
proofProvider.generateProof(mockCircuit, mockWitness)
).rejects.toThrow("Proof server unavailable");
});
});
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { DisclosureModal } from "../DisclosureModal";
describe("DisclosureModal", () => {
it("should render disclosure summary correctly", () => {
const disclosures = [
{ field: "age", label: "Your Age", value: "25" },
{ field: "country", label: "Country", value: "US" },
];
const { container } = render(
<DisclosureModal disclosures={disclosures} onConfirm={() => {}} />
);
expect(container).toMatchSnapshot();
});
});
name: Test Midnight DApp
on: [push, pull_request]
jobs:
unit-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: pnpm install
- run: pnpm test:unit
- run: pnpm test:integration
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: pnpm install
- run: pnpm exec playwright install --with-deps
# E2E with mocks - fast, reliable
- run: pnpm test:e2e:mock
# Optional: E2E with testnet (slower, requires secrets)
# - run: pnpm test:e2e:testnet
# env:
# TESTNET_FAUCET_KEY: ${{ secrets.TESTNET_FAUCET_KEY }}
proof-handling - Understanding what to mock in proof generationwallet-integration - Understanding Lace wallet API to mockerror-handling - Testing error scenariosstate-management - Testing state synchronization/dapp-check - Validates test configuration/dapp-debug tests - Diagnose test failurestools
Use when setting up Midnight development environment, installing Compact compiler and developer tools, configuring proof server, verifying prerequisites, or getting started with Midnight development.
tools
--- name: midnight-tooling:midnight-debugging description: Use when encountering Midnight errors like "compact: command not found", "ERR_UNSUPPORTED_DIR_IMPORT", version mismatches, proof server failures, "@midnight-ntwrk" package errors, or compilation failures. --- # Midnight Environment Debugging Expert knowledge for identifying and resolving common Midnight development toolchain issues. ## Diagnostic Approach When encountering Midnight-related errors, follow this systematic approach: 1.
tools
Use when checking Midnight version compatibility, understanding pragma language_version, verifying compiler and runtime version relationships, or troubleshooting version mismatch errors between Midnight components.
tools
Use when setting up CI/CD for Midnight projects, configuring GitHub Actions for Compact contract compilation, running TypeScript tests in CI, validating version consistency, or automating contract builds.