Skip to main content
info

zkApp programmability is not yet available on the Mina Mainnet. You can get started now by deploying zkApps to the Berkeley Testnet.

How to Test a zkApp

Writing automated tests for your smart contract is essential to ensure your code is well-tested during development.

Test zkApps on a lightweight Mina local network

A Docker image for Mina local networks provides a simple and efficient way to deploy and run lightweight Mina blockchain networks for testing zkApps. The o1labs/mina-local-network Docker image provides:

  • Genesis ledger with pre-funded accounts
  • Accounts manager service
  • Additional port served by reverse proxy to pass requests to the Mina daemon GraphQL endpoint so zkApps work without additional configuration
  • Single commands to launch a single node or multi node network

Pull the o1labs/mina-local-network image from Docker Hub.

Writing tests for your smart contract

All projects created using the Mina zkApp CLI include the Jest JavaScript testing framework. Although you can use any testing framework, the instructions in this document are supported for Jest.

Use the Mina zkApp CLI to create tests for your smart contract.

To scaffold a TypeScript file with a corresponding test file, run:

  • zk file foo

The foo.ts and foo.test.ts files are generated.

The foo.test.ts file is a great place to start writing your smart contract test code. To write valid unit tests, be sure you understand your smart contract functionality.

It's good practice to break down the functionality of your smart contract into describe blocks to organize your test groupings in the same file. By mapping out your unit tests using the describe convention, you provide documentation to developers who read your smart contract.

The following example shows the describe block for test in the foo.test.ts file:

describe('foo.test.ts', () => {
describe('test()', () => {
// your test here
});
});

When you start testing with the Jest testing framework, it is helpful to create describe blocks for all areas where your smart contract modifies its state so you can verify that your state updates as expected.

For basic test examples that deploy and update the state on a smart contract using Jest, see the Add.test.ts file. You can also find this file inside every new project that is created using zk project <name>.

Running tests

To run all test files in your project, run these commands from your project's root directory:

  • npm run test
  • npm run testw for watchmode

To generate a test coverage report for your project, run:

  • npm run coverage

The code coverage result is output to your terminal and shows the percentage of tested code.

Creating a local blockchain

You can quickly test your smart contract by running it locally on a mock blockchain.

The Mina.LocalBlockchain() method specifies a mock Mina ledger of accounts and contains logic for updating the ledger that can be used to test your smart contract.

const Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
tip

To specify whether to generate and verify proofs, you can provide the optional proofsEnabled parameter to the Mina.LocalBlockchain() method:

  • { proofsEnabled: true } - default value, the local instance generates and verifies proofs
  • { proofsEnabled: false } - the local instance does not generate or verify proofs when you want to run tests in the CI or quickly debug your smart contract without waiting for proofs to be generated

You can also programmatically enable and disable the proofsEnabled flag in your test flow by calling Local.setProofsEnabled(x: boolean).

Deploying a contract locally

The mock Mina local blockchain contains test accounts you can use to deploy smart contracts and pay transaction fees.

First, access a test account provided by the local blockchain.

// Local.testAccounts is an array of 10 test accounts that have been pre-filled with Mina
let feePayer = Local.testAccounts[0].privateKey;

Next, generate a zkApp account and a new instance of the smart contract to deploy locally for testing.

// zkapp account
let zkAppPrivateKey = PrivateKey.random();
let zkAppAddress = zkAppPrivateKey.toPublicKey();
let zkAppInstance = new Add(zkAppAddress);

Then, use the test account and zkApp keys to construct a transaction to pay account creation fees and deploy your smart contract. This transaction is sent to the local blockchain.

let txn = await Mina.transaction(feePayer, () => {
AccountUpdate.fundNewAccount(feePayer);
zkAppInstance.deploy({ zkappKey: zkAppPrivateKey });
});

const txPromise = await txn.send();
/*
`txn.send()` returns a promise with two closures - `.wait()` and `.hash()`
`.hash()` returns the transaction hash, as the name might indicate
`.wait()` automatically resolves once the transaction has been included in a block. this is redundant for the LocalBlockchain, but very helpful for live testnets
*/

await txPromise.wait();

Writing integration tests

A well-written integration test verifies the flow of expected behavior for your smart contract and confirms the correctness of state updates. After you test the expected behavior, be sure to verify the preconditions of your methods and test the edge cases of your smart contract.

The following example is a basic integration test script:

describe('Add smart contract integration test', () => {
let feePayer: PrivateKey,
zkAppAddress: PublicKey,
zkAppPrivateKey: PrivateKey,
zkAppInstance: Add,
currentState: Field,
txn;

beforeAll(async () => {
await isReady;
// setup local blockchain
let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

// Local.testAccounts is an array of 10 test accounts that have been pre-filled with Mina
feePayer = Local.testAccounts[0].privateKey;

// zkapp account
zkAppPrivateKey = PrivateKey.random();
zkAppAddress = zkAppPrivateKey.toPublicKey();
zkAppInstance = new Add(zkAppAddress);

// deploy zkapp
txn = await Mina.transaction(feePayer, () => {
AccountUpdate.fundNewAccount(feePayer);
zkAppInstance.deploy({ zkappKey: zkAppPrivateKey });
});
await txn.send();
});

afterAll(async () => {
setTimeout(shutdown, 0);
});

it('sets intitial state of num to 1', async () => {
currentState = zkAppInstance.num.get();
expect(currentState).toEqual(Field(1));
});

it('correctly updates num from intial state to 3', async () => {
txn = await Mina.transaction(feePayer, () => {
zkAppInstance.update();
zkAppInstance.sign(zkAppPrivateKey);
});
txn.send();

currentState = zkAppInstance.num.get();
expect(currentState).toEqual(Field(3));
});
});

Learn more

See the Jest Getting Started documentation.

Next Steps

Now that you know how to test a smart contract, you can learn how to deploy a zkApp.