askill
midnight-dapptesting-patterns

midnight-dapptesting-patternsSafety 95Repository

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.

0 stars
1.2k downloads
Updated 2/6/2026

Package Files

Loading files...
SKILL.md

Testing Patterns

Test Midnight DApps efficiently using mocked providers, simulated wallets, and testnet integration strategies.

When to Use

  • Writing unit tests for contract interaction code
  • Integration testing without real ZK proof generation
  • E2E testing with Playwright or Cypress
  • Setting up CI/CD pipelines for Midnight DApps
  • Testing wallet connection flows without browser extension
  • Validating transaction flows before testnet deployment

Key Concepts

The Testing Challenge

Midnight DApps face unique testing challenges:

ChallengeWhy It MattersSolution
Proof generation takes secondsTests would be too slowMock proof providers
Wallet requires browser extensionCan't run in CI/CDMock wallet provider
Private state is local onlyHard to verify in testsControlled test state
Testnet requires real infrastructureFlaky in automationMock for unit tests, testnet for E2E

Testing Pyramid for Midnight DApps

           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)      \
  /____________________\

Mock vs Real: When to Use Each

Test TypeProof ProviderWallet ProviderUse Case
UnitN/AN/APure business logic
ComponentMockMockUI components
IntegrationMockMockContract interactions
E2E (local)MockMockFull user flows
E2E (testnet)RealReal (Lace)Pre-deployment validation

References

DocumentDescription
mocking-proofs.mdMock proof provider for fast tests
mock-wallet-provider.mdSimulating Lace wallet in tests
testnet-workflows.mdE2E testing against testnet
web3-comparison.mdHardhat/Foundry testing vs Midnight

Examples

ExampleDescription
mock-proof-context/Mock proof provider and test utilities
mock-wallet/Fake wallet implementation for tests
e2e-testnet/Playwright E2E test against testnet

Quick Start

1. Install Test Dependencies

pnpm add -D vitest @testing-library/react @playwright/test msw

2. Create Mock Proof Provider

import { createMockProofProvider } from "./mockProofProvider";

// Returns dummy proofs instantly (no ZK computation)
const mockProofProvider = createMockProofProvider({
  latencyMs: 10, // Simulate realistic timing
});

3. Create Mock Wallet

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 },
};

4. Write a Test

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

Common Patterns

Testing Contract State Reads

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

Testing Witness Execution

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

Testing Error Handling

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

Snapshot Testing for Disclosures

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

CI/CD Integration

GitHub Actions Example

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 }}

Related Skills

  • proof-handling - Understanding what to mock in proof generation
  • wallet-integration - Understanding Lace wallet API to mock
  • error-handling - Testing error scenarios
  • state-management - Testing state synchronization

Related Commands

  • /dapp-check - Validates test configuration
  • /dapp-debug tests - Diagnose test failures

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

86/100Analyzed yesterday

Comprehensive testing patterns skill for Midnight DApps covering unit/integration/E2E testing, mocking strategies for proof providers and wallets, testing pyramid concepts, and CI/CD integration. Well-structured with clear when-to-use section, code examples, and practical patterns. Contains some pseudocode-level imports requiring external context. Deeply nested path suggests internal project use but content is broadly applicable to Midnight DApp testing. High completeness and clarity, good actionability with step-by-step examples.

95
90
80
90
75

Metadata

Licenseunknown
Version-
Updated2/6/2026
Publisheraaronbassett

Tags

apici-cdgithubgithub-actionstesting