Skip to main content

Testing Session Code

This section describes how to test session code using the Casper unit-testing framework. The writing session code section is a prerequisite for this tutorial, which uses the example code described here.

Specifying Dependencies in Cargo.toml

The Cargo.toml sample file in the tests directory contains the test framework dependencies. Specify the dependencies for your tests similarly and update the crate versions. Dependencies may vary with each project. These are the basic dependencies the testing framework requires:

[dev-dependencies]
casper-engine-test-support = { version = "2.2.0", features = ["test-support"] }
casper-execution-engine = "2.0.0"
casper-types = "1.5.0"
  • casper-execution-engine - This crate imports the execution engine functionality, enabling Wasm execution within the test framework. Each node contains an instance of an execution engine, and the testing framework simulates this behavior.
  • casper-engine-test-support - A helper crate that provides the interface to write tests and interact with an instance of the execution engine.
  • casper-types - Types shared by many Casper crates for use on a Casper network.

Writing the Tests

Tests for this example session code reside in the tests/src/integration-tests.rs file.

Notice that this file contains an empty main method to initialize the test program. Alternatively, we could use the #![no_main] annotation at the top of the file, as we did here.

fn main() {
panic!("Execute \"cargo test\" to test the contract, not \"cargo run\".");
}

The #[cfg(test)] attribute tells the Rust compiler to compile and run the tests only when invoking cargo test, not while debugging or releasing. All testing functions reside within the grouping mechanism mod tests.

#[cfg(test)]
mod tests {
// The entire test program resides here
}

Importing Required Packages

Next, import the packages required for the tests to run. The example tests use these packages:

    use casper_engine_test_support::{
ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
DEFAULT_RUN_GENESIS_REQUEST,
};
use casper_types::account::AccountHash;
use casper_types::{runtime_args, RuntimeArgs};

Defining The Constants

The names of the runtime arguments are defined as constants. Using the exact names as in the original contract class is mandatory to define these constants. These are dictated by the arguments specified by the session code. If your session code takes in different arguments, you should define them as constants at this point.

const ASSOCIATED_ACCOUNT_HASH: AccountHash = AccountHash::new([1u8; 32]); // hash of the associated account
const ASSOCIATED_ACCOUNT: &str = "deployment-account"; // the associated account argument
const CONTRACT_WASM: &str = "contract.wasm"; // file to pass to the instance of the EE

Creating a Test Function

In this step, we create a program to test the session code. The bodies of test functions typically perform some setup, run the code, then verify the results using assertions. Each test function is annotated with the #[test] attribute.

#[test]
fn <unit-test-name>{
// Test function implementation
}

This unit test is a good example of testing session code. At a high level, the test follows this process:

  1. Initialize an instance of the execution engine and the InMemoryWasmTestBuilder.
    let mut builder = InMemoryWasmTestBuilder::default();
  1. Execute the genesis process.
    builder.run_genesis(&*DEFAULT_RUN_GENESIS_REQUEST).commit();
  1. Execute the test-specific logic. In this example, retrieve information about the account running the session code and its associated keys. For full details, visit GitHub.

  2. Retrieve runtime arguments, which should be the same as defined in the contract.

  3. Create the execution request that sets up the session code to be processed. In this example, the CONTRACT_WASM is the session code.

    let execute_request =
ExecuteRequestBuilder::standard(*DEFAULT_ACCOUNT_ADDR, CONTRACT_WASM, runtime_args)
.build();
  1. Invoke the execution engine to process the session code.
    builder.exec(execute_request).expect_success().commit();
  1. Verify that the results match the expected output. This example checks the associated keys.
    assert!(associated_keys.contains_key(&ASSOCIATED_ACCOUNT_HASH));

Running the Test

This example uses a Makefile to run the tests.

make test

Under the hood, the Makefile generates a tests/wasm folder, copies the Wasm to the folder, and runs the tests with cargo test.

mkdir -p tests/wasm
cp contract/target/wasm32-unknown-unknown/release/contract.wasm tests/wasm
cd tests && cargo test

Other Examples

In the counter unit tests, we use session code to call the contract. The code loads the account that pays for the session code, the session code Wasm, and the runtime arguments. Then, the code invokes the execution engine to process the session code.

    // Use session code to increment the counter.
let session_code_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
COUNTER_CALL_WASM,
runtime_args! {
CONTRACT_KEY => contract_v1_hash
},
)
.build();

builder.exec(session_code_request)
.expect_success()
.commit();

The verification step looks like this:


let incremented_count = builder
.query(None, count_key, &[])
.expect("should be stored value.")
.as_cl_value()
.expect("should be cl value.")
.clone()
.into_t::<i32>()
.expect("should be i32.");

assert_eq!(incremented_count, 1);

For many more examples, visit the casper-node GitHub repository.

Video Walkthrough

The following brief video describes testing the sample session code for configuring an account.

What's Next?