Please note that zkApp programmability is not yet available on Mina Mainnet, but zkApps can now be deployed to 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. 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.
Writing tests for your smart contract
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);
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.