Integration Tests
Integration tests enable you to deploy a contract in the NEAR testnet
or a local sandbox
and create test users to interact with it. This way, you can thoroughly test your contract in a realistic environment.
Moreover, when using the local sandbox
you gain complete control of the network:
- Create test
Accounts
and manipulate theirState
andBalance
. - Simulate errors on callbacks.
- Control the time-flow and fast-forward into the future (Rust ready, TS coming soon).
In NEAR, integration tests are implemented using a framework called Workspaces. Workspaces comes in two flavors: 🦀 Rust and 🌐 Typescript.
All of our examples come with integration testing.
NEAR Workspaces allows you to write tests once, and run them either on testnet
or a local Sandbox
. By default, Workspaces will start a sandbox and run your tests locally. Lets dive into the features of our framework and see how they can help you.
Create Accounts
Dev Account
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Subaccount
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Using Secret Key
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Using Credentials From File
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
WASM Files
Compile Contract Code
- 🦀 Rust
- 🌐 JavaScript
Loading...
You don't need to assert compiling process everytime. You can use ?
operator to get the result as Vec<u8>
without dealing with Result<Vec<u8>>, Error>
type. That way you can directly use this vector to deploy the wasm file into account. Your test will still fail if compiling process fails.
let contract_wasm = near_workspaces::compile_project("./").await?;
If you want to compile a contract each time running tests, you can put following scripts into package.json
file. In the code you can access path to compiled file using process.argv[2]
.
package.json
file:
"scripts": {
"build": "near-sdk-js build src/contract.ts build/hello_near.wasm",
"test": "$npm_execpath run build && ava -- ./build/hello_near.wasm"
},
main.ava.js
file:
const pathToWasm = process.argv[2];
await contract.deploy(pathToWasm);
Loading From File
- 🦀 Rust
- 🌐 JavaScript
Loading...
The same as in the case of compilation wasm from code, you don't need to assert reading file process everytime. You can use expect
method to get the reading file result as Vec<u8>
and provide error message as a parameter. Your test will still fail if compiling process fails.
let contract_wasm = std::fs::read(artifact_path)
.expect(format!("Could not read WASM file from {}", artifact_path).as_str());
If you want to use pre-compiled a contract, you can put following scripts into package.json
file. In the code you can access path to pre-compiled file using process.argv[2]
.
package.json
file:
"scripts": {
"build": "near-sdk-js build src/contract.ts build/hello_near.wasm",
"test": "ava -- ./build/hello_near.wasm"
},
main.ava.js
file:
const pathToWasm = process.argv[2];
await contract.deploy(pathToWasm);
Deploy Contracts
Dev Deploy
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Deploy To Account
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Logs
Show contract's logs.
- 🦀 Rust
- 🌐 JavaScript
You can use println
or dbg!
when you want to see information from your code.
Loading...
In Rust, the output from your code is captured by default and not displayed in the terminal. In order to see the output, you have to use the --nocapture
flag
eg. cargo test -- --nocapture
If you want to access the contracts logs, you can find them in the tx_outcome.logs()
Vec.
let tx_outcome = user_account
.call(contract.id(), "set_greeting")
.args_json(json!({"greeting": "Hello World!"}))
.transact()
.await?;
assert!(tx_outcome.is_success());
dbg!(tx_outcome.logs());
// [tests/test_basics.rs:29:5] tx_outcome.logs() = [
// "Saving greeting: Hello World!",
// ]
Use console.log
method when you want to see debug information from your code.
const balance = await account.balance();
console.log('balance: ', balance);
// balance: {
// total: <BN: 52b7d2dcc80cd2e4000000>,
// stateStaked: <BN: 62a992e53a0af00000>,
// staked: <BN: 0>,
// available: <BN: 52b77033352798d9100000>
// }
Account Balance
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Transactions
Call
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
View
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
Patch State on the Fly
In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with patchState
.
You can alter contract code, accounts, and access keys using normal transactions via the DeployContract
, CreateAccount
, and AddKey
actions. But this limits you to altering your own account or sub-account. patchState
allows you to perform these operations on any account.
- 🦀 Rust
- 🌐 JavaScript
Loading...
Loading...
To see a complete example of how to do this, see the patch-state test.
As an alternative to patchState
, you can stop the node, dump state at genesis, edit the genesis, and restart the node.
This approach is more complex to do and also cannot be performed without restarting the node.
Time Traveling
workspaces
offers support for forwarding the state of the blockchain to the future. This means contracts which require time sensitive data do not need to sit and wait the same amount of time for blocks on the sandbox to be produced. We can simply just call worker.fast_forward
to get us further in time:
- 🦀 Rust
- 🌐 JavaScript
Loading...
Using Testnet
NEAR Workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against NEAR TestNet. Some reasons this might be helpful:
- Gives higher confidence that your contracts work as expected
- You can test against deployed testnet contracts
- If something seems off in Sandbox mode, you can compare it to testnet
- 🦀 Rust
- 🌐 JavaScript
Loading...
If you can create a new account on each iteration as well.
You can switch to testnet mode in three ways:
1. Setting the Worker
network to testnet
When creating Worker set network to testnet
and pass your master account (an account for which you have the private key):
const worker = await Worker.init({
network: 'testnet',
testnetMasterAccountId: '<yourAccountName>',
initialBalance: NEAR.parse("<X> N").toString(),
})
2. Setting environment variables
Set the NEAR_WORKSPACES_NETWORK
and TESTNET_MASTER_ACCOUNT_ID
(an account for which you have the private key) environment variables when running your tests:
NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID=<your master account Id> node test.js
If you set this environment variables and pass {network: 'testnet', testnetMasterAccountId: <masterAccountId>}
to Worker.init
, the config object takes precedence.
3. Config file
If you are using AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation.
Create a file in the same directory as your package.json
called ava.testnet.config.cjs
with the following contents:
module.exports = {
...require('near-workspaces/ava.testnet.config.cjs'),
...require('./ava.config.cjs'),
};
module.exports.environmentVariables = {
TESTNET_MASTER_ACCOUNT_ID: '<masterAccountId>',
};
Where the master account is an account for which you have the private key.
The near-workspaces/ava.testnet.config.cjs import sets the NEAR_WORKSPACES_NETWORK
environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode.
Now you'll also want to add a test:testnet
script to your package.json
's scripts
section:
"scripts": {
"test": "ava",
+ "test:testnet": "ava --config ./ava.testnet.config.cjs"
}
To use the accounts, you will need to create the .near-credentials/workspaces/testnet
directory and add files for your master account, for example:
// .near-credentials/workspaces/testnet/<your-account>.testnet.json
{"account_id":"<your-account>.testnet","public_key":"ed25519:...","private_key":"ed25519:..."}
Example:
Loading...
Spooning Contracts
Spooning a blockchain is copying the data from one network into a different network. NEAR Workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment:
- 🦀 Rust