Smart Contracts Integration Testing
In NEAR, integration tests are implemented using a framework called Workspaces. It comes in two flavors, Rust and Typescript. However, for this document purposes we'll focus on the Rust flavor for obvious reasons.
You can also check official NEAR Unit test documentation for more details
Adding Near Workspaces to your project
Near Workspaces for Rust repo: near-workspaces-rs
To add Workspaces to your project just add it to your Cargo.toml file in the [dev-dependencies] section, don't add it to the dependencies section since Workspaces does not currently compile to WASM.
[dev-dependencies]
near-sdk = { version = "5.7", features = ["unit-testing"] }
near-workspaces = { version = "0.16", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
serde_json = "1"
Testing using Workspaces
The following code snippet is based on the Hello Contract example, this is a Rust example since there is no example of instantiating a contract from Scratch on Near Workspaces documentation using Rust.
#![allow(unused)] fn main() { #[tokio::test] async fn test_hello_contract() -> anyhow::Result<()> { let worker = near_workspaces::sandbox().await?; let contract_wasm: Vec<u8> = near_workspaces::compile_project("./").await?; let contract = worker.dev_deploy(&contract_wasm).await?; let account = worker.dev_create_account().await?; let greeting: serde_json::Value = contract .view("get_greeting") .await? .json()?; assert_eq!(greeting, "Hello"); let outcome = account .call(contract.id(), "set_greeting") .args_json(json!({ "greeting": "Hola Mundo", })) .transact() .await?; println!("set_greeting outcome: {:#?}", outcome); let greeting: serde_json::Value = contract .view("get_greeting") .await? .json()?; assert_eq!(greeting, "Hola Mundo"); Ok(()) } }
Code Breakdown
Test Annotation
#![allow(unused)] fn main() { #[tokio::test] async fn test_hello_contract() -> anyhow::Result<()> { }
- #[tokio::test]: Marks the function as an asynchronous test using the tokio runtime.
- async fn test_hello_contract() -> anyhow::Result<()>: Defines an asynchronous test function that returns a Result type from the anyhow crate for error handling.
Initialize Sandbox Environment
#![allow(unused)] fn main() { let worker = near_workspaces::sandbox().await?; }
- Initializes the NEAR Workspaces sandbox environment, which simulates the NEAR blockchain for testing.
Compile and Deploy Contract
#![allow(unused)] fn main() { let contract_wasm: Vec<u8> = near_workspaces::compile_project("./").await?; let contract = worker.dev_deploy(&contract_wasm).await?; }
- Compiles the smart contract located in the current directory ("./") and stores the compiled Wasm bytecode in
contract_wasm. - Deploys the compiled contract to the sandbox environment and stores the deployed contract instance in
contract.
Create a Test Account
#![allow(unused)] fn main() { let account = worker.dev_create_account().await?; }
- Creates a new test account in the sandbox environment.
Call get_greeting Method
#![allow(unused)] fn main() { let greeting: serde_json::Value = contract .view("get_greeting") .await? .json()?; assert_eq!(greeting, "Hello"); }
- Calls the
get_greetingview method on the deployed contract. - Parses the returned JSON value and stores it in greeting.
- Asserts that the initial greeting is "Hello".
Call set_greeting Method
#![allow(unused)] fn main() { let outcome = account .call(contract.id(), "set_greeting") .args_json(json!({ "greeting": "Hola Mundo", })) .transact() .await?; println!("set_greeting outcome: {:#?}", outcome); }
- Calls the
set_greetingmethod on the contract, passing the new greeting "Hola Mundo" as an argument. - Executes the transaction and stores the outcome.
- Prints the outcome of the
set_greetingcall.
Verify Updated Greeting
#![allow(unused)] fn main() { let greeting: serde_json::Value = contract .view("get_greeting") .await? .json()?; assert_eq!(greeting, "Hola Mundo"); }
- Calls the get_greeting method again to verify the updated greeting.
- Asserts that the greeting has been updated to "Hola Mundo".