7.3 Doko JS testing framework

In this module, we’ll walk through writing and running tests using the DokoJS framework. DokoJS gives a powerful yet intuitive toolkit for writing and running tests for Leo programs. It will generate automatically typescript code for interacting with and testing your program. Here, we'll look looking closely at how contracts are interacted with in TypeScript, how test files are structured, and how to run your tests in different modes. If you’ve used Jest or Mocha before, DokoJS will feel right at home. But since we’re dealing with zero-knowledge proofs, private programs, and encrypted records, we’ll need to handle a few extra things under the hood.

Intallation

First head to: https://github.com/venture23-aleo/doko-js

And make sure to follow all the instructions to install latest version of Doko JS.

Setting Up Doko JS

Initialize a new project by giving the name of the project.

dokojs init <PROJECT_NAME>

First, make sure your project is initialized and compiled. You should already have this structure (or something like it):

my-project/
├── programs/
│   └── token.leo
├── artifacts/
│   ├── js/
│   └── leo/
├── test/
│   └── token.test.ts
├── aleo-config.js
└── ...

If you don’t have a test/ directory yet, create one now. This is where all your .test.ts files will live.

You should then update the aleo-config.js file with custom key and network environement variable values.

Adding or Modifying a Program

To add a new Leo program, simply create a new file inside the programs/ directory. If you're updating an existing one, you can either modify the file directly or use the CLI command:

dokojs add [PROGRAM_NAME]

This streamlines program setup and ensures consistency across your project.

Compilation

Once your programs are in place, compile the project by running:

dokojs compile

This command generates an artifacts/ folder containing two key directories:

  • leo – This folder holds the compiled Leo packages. For every .leo file inside programs/, a corresponding package is created here. DokoJS copies your Leo source code into src/main.leo, compiles it, and if successful, produces .aleo files and build artifacts.

  • js – This folder contains everything needed to work with your Leo programs in TypeScript. It includes:

    • Type definitions (types/)

    • Type conversion helpers (js2leo for converting JS to Leo, leo2js for the reverse)

    • Program interfaces (.ts files with transitions and mappings)

Each program gets its own fully-typed interface, making it easy to interact with your contracts in tests, scripts, or production code.

Now let’s take a look at a test file.

Writing Your First DokoJS Test

Here's an example test suite for a simple token contract:

import { ExecutionMode } from '@doko-js/core';
import { TokenContract } from '../artifacts/js/token';
import { decrypttoken } from '../artifacts/js/leo2js/token';
import { PrivateKey } from '@provablehq/sdk';

const TIMEOUT = 200_000;

const mode = ExecutionMode.SnarkExecute;
const contract = new TokenContract({ mode });

const [admin] = contract.getAccounts();
const recipient = process.env.ALEO_DEVNET_PRIVATE_KEY3;

describe('Token Contract', () => {
  test('deploy', async () => {
    const tx = await contract.deploy();
    await tx.wait();
  }, TIMEOUT);

  test('mint public tokens', async () => {
    const amount = BigInt(5000);
    const tx = await contract.mint_public(admin, amount);
    await tx.wait();

    const balance = await contract.account(admin);
    expect(balance).toBe(amount);
  }, TIMEOUT);

  test('mint private tokens', async () => {
    const amount = BigInt(10000);
    const tx = await contract.mint_private(admin, amount);
    const [record] = await tx.wait();

    const decrypted = decrypttoken(record, process.env.ALEO_PRIVATE_KEY_TESTNET3);
    expect(decrypted.amount).toBe(amount);
  }, TIMEOUT);
});

Let’s break this down.

Anatomy of a Test File

  • Importing the contract: TokenContract is auto-generated during compilation. It gives you typed access to all contract methods.

  • Execution Mode: You can choose between SnarkExecute and LeoRun. Use the former to broadcast transactions to the blockchain; use the latter for local dry-runs.

  • Getting Accounts: contract.getAccounts() returns addresses derived from your private keys. This maps to what's in your aleo-config.js.

  • Deploying and Interacting: You deploy using contract.deploy(), then call methods like mint_public and mint_private, and assert outcomes with expect().

Remember: private values (like token amounts) are encrypted. To assert them, you’ll need to decrypt the record using helper functions like decrypttoken. automatically generated

Running Your Tests

To run all tests in your project, simply run:

npm run test

To run a specific test file, pass the filename (or just part of it):

npm run test -- token

This will run any test files matching token.*.ts.

Or run it directly with Jest for more control:

npm test -- --runInBand test/token.test.ts

The --runInBand flag ensures your tests run sequentially, which helps when dealing with stateful blockchain tests.

Under the Hood: Where the Magic Happens

When you compile your project (dokojs compile), DokoJS generates JS bindings for each Leo program. These files live inside artifacts/js/ and include:

artifacts/
└── js/
    ├── token.ts              <-- Contract methods
    ├── js2leo/token.ts       <-- Type conversion: JS -> Leo
    ├── leo2js/token.ts       <-- Type conversion: Leo -> JS
    └── types/token.ts        <-- TypeScript definitions

So when you write:

import { TokenContract } from '../artifacts/js/token';

You're importing a wrapper around your Leo contract that handles all the encoding, decoding, and blockchain interactions for you. Pretty handy.

Configuration Tips

Everything is wired through aleo-config.js. This file defines:

  • Private keys

  • Network endpoints

  • Execution modes

  • Priority fees

Here’s a quick peek at a minimal config:

export default {
  accounts: [process.env.ALEO_PRIVATE_KEY],
  mode: 'execute',
  networks: {
    testnet: {
      endpoint: 'http://localhost:3030',
      accounts: [process.env.ALEO_DEVNET_PRIVATE_KEY1],
      priorityFee: 0.01
    }
  },
  defaultNetwork: 'testnet'
};

Want to switch to local mode? Just change the mode to 'evaluate'.

Decrypting Private Records

When using mint_private or transfer_private, the resulting records are encrypted on-chain. To verify values, use decrypttoken() (or your equivalent based on the contract name).

import { decrypttoken } from '../artifacts/js/leo2js/token';

const decrypted = decrypttoken(record, PRIVATE_KEY);
expect(decrypted.amount).toBe(expectedAmount);

The best way to understand how Doko JS works and how it can be useful for you is to try it directly with your own programs : look into the code it automatically generated on compilation, and writing a few tests on your own.

Last updated