From midnight-dapp
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/midnight-dapp:skills/testing-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Test Midnight DApps efficiently using mocked providers, simulated wallets, and testnet integration strategies.
examples/e2e-testnet/testnet.spec.tsexamples/e2e-testnet/testnetSetup.tsexamples/mock-proof-context/mockProofProvider.tsexamples/mock-proof-context/testUtils.tsexamples/mock-wallet/MockWallet.tsexamples/mock-wallet/walletTestUtils.tsreferences/mock-wallet-provider.mdreferences/mocking-proofs.mdreferences/testnet-workflows.mdreferences/web3-comparison.mdTest 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 failuresnpx claudepluginhub aaronbassett/midnight-knowledgebase --plugin midnight-dappThis skill should be used when the user asks about the Midnight.js SDK, midnight-js packages, @midnight-ntwrk npm packages, setting up SDK providers, deploying or finding contracts with deployContract or findDeployedContract, calling circuits with callTx or submitCallTx, the transaction lifecycle, SDK provider types (WalletProvider, MidnightProvider, PublicDataProvider, ProofProvider, ZkConfigProvider, PrivateStateProvider), testkit-js testing, observable state subscriptions, contract maintenance and verifier keys, or connecting to the indexer or proof server.
Verification by running E2E scripts against a local Midnight devnet. Writes SDK test scripts (raw or using testkit-js) that exercise the full transaction pipeline: deploy, call circuits, observe state. Checks devnet health before proceeding. Loaded by the sdk-tester agent. Loads the `midnight-tooling:devnet` skill for infrastructure management.
This skill should be used when the user asks about Midnight network architecture, transaction structure, guaranteed vs fallible sections, Zswap/Kachina integration, ledger and state management, cryptographic binding, balance verification, nullifiers, address derivation, transaction merging, atomic swaps, fee handling, or the privacy model separating private and public domains.