testing

SKILL.md

Testing Midnight Contracts

Test Compact smart contracts using simulators and test frameworks.

Quick Start

import { ContractSimulator } from '@midnight-ntwrk/compact-simulator';

// Create simulator
const simulator = new ContractSimulator(compiledContract);

// Call circuit
const result = await simulator.call('increment', {});

// Check state
expect(simulator.ledger.counter).toBe(1n);

Reference Files

Topic Resource
Simulator Setup references/simulator-setup.md
Test Patterns references/test-patterns.md
Debugging references/debugging.md

Test Environment

┌──────────────────────────────────────────────┐
│              Test Environment                │
├──────────────────────────────────────────────┤
│  ┌────────────────┐  ┌────────────────┐     │
│  │   Contract     │  │    Contract    │     │
│  │   Simulator    │  │    Artifacts   │     │
│  └────────────────┘  └────────────────┘     │
│           │                  │               │
│           └────────┬─────────┘               │
│                    ▼                         │
│           ┌────────────────┐                 │
│           │   Test Suite   │                 │
│           │   (Jest/Vitest)│                 │
│           └────────────────┘                 │
└──────────────────────────────────────────────┘

Installation

npm install -D @midnight-ntwrk/compact-simulator vitest

Basic Test Structure

import { describe, it, expect, beforeEach } from 'vitest';
import { ContractSimulator } from '@midnight-ntwrk/compact-simulator';
import { setNetworkId, NetworkId } from '@midnight-ntwrk/midnight-js-network-id';

describe('MyContract', () => {
  let simulator: ContractSimulator;

  beforeEach(() => {
    setNetworkId(NetworkId.Undeployed);
    simulator = new ContractSimulator(compiledContract);
  });

  it('should initialize with zero', async () => {
    expect(simulator.ledger.counter).toBe(0n);
  });

  it('should increment counter', async () => {
    await simulator.call('increment', {});
    expect(simulator.ledger.counter).toBe(1n);
  });
});

Testing Patterns

State Verification

it('should update ledger state', async () => {
  // Initial state
  expect(simulator.ledger.message).toBe('');

  // Call circuit
  await simulator.call('setMessage', { input: 'Hello' });

  // Verify state
  expect(simulator.ledger.message).toBe('Hello');
});

Error Testing

it('should reject invalid input', async () => {
  await expect(simulator.call('withdraw', { amount: 1000n })).rejects.toThrow('Assertion failed');
});

Privacy Testing

it('should not reveal private inputs', async () => {
  const result = await simulator.call('checkBalance', {
    balance: 1000n,
    required: 500n,
  });

  // Result is boolean, not actual balance
  expect(result).toBe(true);
  // Ledger should not contain balance
  expect(simulator.ledger.balance).toBeUndefined();
});

Best Practices

  • ✅ Test each circuit function independently
  • ✅ Verify state changes after each call
  • ✅ Test error conditions and edge cases
  • ✅ Mock witnesses for privacy testing
  • ✅ Use NetworkId.Undeployed for testing
  • ❌ Don't test proof generation (use simulator)
  • ❌ Don't rely on network in unit tests

Test Categories

Category Tests
Unit Individual circuit functions
Integration Multi-circuit workflows
State Ledger state transitions
Error Assertion failures
Privacy Data not leaked

Running Tests

# Run all tests
npm test

# Run with coverage
npm test -- --coverage

# Run specific test file
npm test -- counter.test.ts

# Watch mode
npm test -- --watch
Weekly Installs
3
First Seen
Jan 31, 2026
Installed on
gemini-cli3
opencode2
replit2
antigravity2
claude-code2
github-copilot2