Testing your Solana programs is a critical part of the development process toensure that your program behaves as expected and can even speedup yourdevelopment. This guide will walk you through how you can test your Solanaprograms using Bankrun, a superfast test runner for Solana programs. .
Most Solana tests use the Mocha framework for writingthe tests and Chai for assertions. However, you canuse any testing framework that you are comfortable with. In this guide we willhave a look at Jest andBankrun. With Bankrun, you canaccelerate your tests by almost 10x, gain the ability to modify program time,and write custom account data.
Presets #
There are a few presets that will set you up with a basic testing environmentfor your Solana programs. These presets are for example:
npx create-solana-dapp my-dapp
create-solana-dapp
will set you up with a Solana web project with variousconfiguration options, including a Next.js or React client, the Tailwind UILibrary, and a simple Anchor program. The tests are written using Jest and canbe run with the anchor test
command.
npx create-solana-game my-game
create-solana-game
will set you up with a Solana game project that includesJest, Mocha andBankrun tests and a NextJS appapp and an additional Unity Game engine client using Solana Wallet adapter. TheMocha and Bankrun tests can both be run using the anchor test
command.
You can also find many test examples in theSolana Program Examples.
Anchor test #
Using the Anchor framework, you can run anchor test
command to perform thepre-configured test
command within the anchor.toml
file.
Anchor.toml
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
This will run the tests in the tests directory using the ts-mocha
command witha timeout of 1,000,000 milliseconds.
What anchor does here is that it starts up a local validator, deploys theprogram from your anchor workspace and runs the test against the defined networkin the Anchor.toml
.
Info
Tip: You can also run anchor test --detach
to let the validator continuerunning after the tests have finished which lets you inspect your transactionsin the Solana Explorer.
You can also define your own test command in the anchor.toml
file. For exampleyou can first run the mocha tests against the local validator and then run thejest tests using bankrun by combining them using &&
:
Anchor.toml
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts && yarn run jest"
This would run the mocha tests first and then run the jest tests.
You can also define your own test command. For example:
Anchor.toml
super_test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 only_super_test.ts"jest_tests = "yarn test"
These you would then run using:
anchor run super_testanchor run jest_tests
Info
Note though that in this case anchor does not start a local validator for you.So you will need to deploy the program yourself to your local cluster or runthem against a public test network. Also Anchor environment variables willonly be available when run trough the anchor test
commands not when runningtests with yarn test
for example.
Migrating from Mocha to Jest #
In this part we will learn how to migrate from Mocha toJest. Jest is another Javascript testing framework similarto Mocha. It already has an integrated test runner so you don't need to use Chaianymore.
First you need to install Jest:
yarn add --dev jest
Then you add a new command to your package.json
:
package.json
{ "scripts": { "test": "jest" }}
Then you can run the tests with:
yarn test
Since we want to run our tests with Typescript we need to install the ts-jest
package and also create a Jest configuration file:
yarn add --dev ts-jest @jest/globals @types/jestyarn ts-jest config:init
This will create a jest.config.js
file in your project. Now you can updateyour Anchor.toml
file to run the Jest tests:
Anchor.toml
test = "yarn test"
Jest Troubleshooting #
- In case you get a
SyntaxError: Cannot use import statement outside a module
error, you either did not create a jest config or you need to add the
following to yourjest.config.js
file:
jest.config.js
module.exports = { transform: { "^.+\\.tsx?$": "ts-jest", }, testEnvironment: "node", moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],};
- Since running tests against the local validator can take quite some time, ifyou get an error saying that you can not log after the test is finished yourtest is probably timing out. Different from Mocha, Jest does not have adefault timeout. You can set a timeout in your
jest.config.js
file:
jest.config.js
module.exports = { testTimeout: 10000,};
Or you can set a timeout for a single test:
your.test.js
test("test name", async () => { // your test code}, 10000);
- If you get an error saying that Anchor environment variables are missing youare probably trying to use the AnchorProvider without running the teststhrough
anchor test
oranchor run test
. Just update your Anchor.toml torun the Jest testsyarn test
to run them in the anchor environment with allthe environment variables set.
Bankrun #
Instead of using solana-test-validator
you can also useSolana Bankrun. It actssimilarly to the local validator but is more lightweight and faster. Some reallyhelpful features arewriting custom account dataandtime travelwhich makes it much easier to test time based programs and programs that rely onspecific account data.
To use Bankrun and Bankrun Anchor you need to add it to your package.json
:
yarn add solana-bankrun anchor-bankrun
To switch from a Mocha Anchor test to aBankrun Anchor test you onlyneed to change the provider to be aBankrunProvider
and create a context using startAnchor
within each of your test files:
my-test.test.ts
// ... imports heredescribe('My test', () => { test('Test allocate counter + increment tx', async () => { const context = await startAnchor(".", [], []); const client = context.banksClient; const provider = new BankrunProvider(context); anchor.setProvider(provider); // ... testing logic }}
startAnchor
will automatically add your program from your Anchor workspace tothe Bankrun bank. You can alsoadd additional accounts and programsto the bank by passing them in the startAnchor
function. There are a fewthings that are different to running tests against the local validator thoughwhich we will cover in the next section.
Bankrun differences to the local validator #
Bankrun uses a BanksServer (a simplified version ofSolana's bankthat processes transactions and manages accounts) and a BanksClient to simulatethe Solana network, enabling advanced testing features like time manipulationand dynamic account data setting. Unlike the solana-test-validator
, bankrunallows efficient testing by running against a local instance of the networkstate, making it ideal for testing Solana programs without the overhead of afull validator. The behaviour for transactions is pretty similar, but there area few differences to the local validator:
Airdrops #
- Bankrun does not support Airdrops. The standard signer used in theBankrunProvider will be automatically funded with some Sol. If you needanother funded account you can create one by passing in an additional accountin the
startAnchor
function.
let secondKeypair: Keypair = new anchor.web3.Keypair(); context = await startAnchor( "",[], [ { address: secondKeypair.publicKey, info: { lamports: 1_000_000_000, // 1 SOL equivalent data: Buffer.alloc(0), owner: SYSTEM_PROGRAM_ID, executable: false, }, }, ]);provider = new BankrunProvider(context);
Confirming transactions #
Since Bankrun is directly working on the bank you will not need to confirm yourtransactions. So the connection.confirmTransaction()
function will not beavailable. You can just leave it out.
Getting account data #
While you can still use connection.getAccount
to retrieve account data, thepreferred method in the bankrun framework is to use client.getAccount
, whichreturns a Promise<Account>
. This method aligns better with the testingframework's design. However, if you prefer consistency with how accounts areretrieved in the rest of your Solana codebase, you can continue usingconnection.getAccount. Choose the method that best fits your specific use case.
await client.getAccount(playerPDA).then(info => { const decoded = program.coder.accounts.decode( "playerData", Buffer.from(info.data), ); console.log("Player account info", JSON.stringify(decoded)); expect(decoded).toBeDefined(); expect(parseInt(decoded.energy)).toEqual(99);});
Signing transactions with another keypair #
By default when using program.function.rpc()
the transaction will beautomatically signed with the provider.wallet
keypair. If you want to sign thetransaction with another keypair you can create a second provider and then usethat one to sign transaction with another keypair.
let secondKeypair: Keypair = new anchor.web3.Keypair(); let context = await startAnchor("",[],[ { address: secondKeypair.publicKey, info: { lamports: 1_000_000_000, data: Buffer.alloc(0), owner: SYSTEM_PROGRAM_ID, executable: false, }, }, ]);beneficiaryProvider = new BankrunProvider(context);beneficiaryProvider.wallet = new NodeWallet(secondKeypair); secondProgram = new Program<Vesting>(IDL as Vesting, beneficiaryProvider);
Using Bankrun for native programs #
You can also use Bankrun fornative programs. The maindifference is that you use start
instead of startAnchor
to start the Bankrunbank. You can then use the client
to interact with the bank.
const context = await start( [{ name: "counter_solana_native", programId: PROGRAM_ID }], [],);const client = context.banksClient;
Instead of using program.instruction().rpc()
you can use theawait client.processTransaction(tx)
.
In the Solana program examples you can find afull native Bankrun example.
Bankrun trouble shooting #
If you encounter an
Unknown action 'undefined'
error when sending atransaction using Bankrun, you are likely trying to send two identicaltransactions with the same blockhash. Request a new recent blockhash beforesending the second transaction or add some seed or parameter to yourinstructions to make sure they will result in different transaction hashes.If you encounter
Clock handle timeout
error you can just restart yourterminal and run the tests again.
Conclusion #
Testing your Solana programs is essential for ensuring they behave as expected.Bankrun offers a lightweight and fast alternative to the local validator, makingyour tests up to 10 times faster. It enables powerful features like customaccount data and time travel, which can significantly enhance your testingcapabilities. Additionally, Jest is a great alternative to Mocha for writingtests and can be easily integrated with Bankrun.
However, it's important to note a few disadvantages of using Bankrun compared tothe local validator:
- Environment Representation: Tests run with Bankrun may not fully represent alive or testnet environment.
- Code Reusability: Some code used in local validator tests might not bereusable with Bankrun.
- Dependency: Using Bankrun and Bankrun Anchor introduces dependencies specificto these tools.
Despite these drawbacks, Bankrun is a valuable tool that can greatly improveyour development workflow.