Writing Tests
Linera applications can be tested using normal Rust unit tests or integration tests. Unit tests use a mock runtime for execution, so it's useful for testing the application as if it were running by itself on a single chain. Integration tests use a simulated validator for testing. This allows creating chains and adding blocks to them in order to test interactions between multiple microchains and multiple applications.
Applications should consider having both types of tests. Unit tests should be used to focus on the application's internals and core functionality. Integration tests should be used to test how the application behaves on a more complex environment that's closer to the real network. Both types of test are running in native Rust.
For Rust tests, the
cargo test
command can be used to run both the unit and integration tests.
Unit tests
Unit tests are written beside the application's source code (i.e., inside the
src
directory of the project). The main purpose of a unit test is to test
parts of the application in an isolated environment. Anything that's external is
usually mocked. When the linera-sdk
is compiled with the test
feature
enabled, the ContractRuntime
and SystemRuntime
types are actually mock
runtimes, and can be configured to return specific values for different tests.
Example
A simple unit test is shown below, which tests if the method execute_operation
method changes the application state of the Counter
application.
#[test]
fn operation() {
let runtime = ContractRuntime::new().with_application_parameters(());
let state = CounterState::load(runtime.root_view_storage_context())
.blocking_wait()
.expect("Failed to read from mock key value store");
let mut counter = CounterContract { state, runtime };
let initial_value = 72_u64;
counter
.instantiate(initial_value)
.now_or_never()
.expect("Initialization of counter state should not await anything");
let increment = 42_308_u64;
let response = counter
.execute_operation(increment)
.now_or_never()
.expect("Execution of counter operation should not await anything");
let expected_value = initial_value + increment;
assert_eq!(response, expected_value);
assert_eq!(*counter.state.value.get(), initial_value + increment);
}
Integration tests
Integration tests are usually written separately from the application's source
code (i.e., inside a tests
directory that's beside the src
directory).
Integration tests use the helper types from linera_sdk::test
to set up a
simulated Linera network, and publish blocks to microchains in order to execute
the application.
Example
A simple integration test that execution a block containing an operation for the
Counter
application is shown below.
#[tokio::test(flavor = "multi_thread")]
async fn single_chain_test() {
let (validator, module_id) =
TestValidator::with_current_module::<counter::CounterAbi, (), u64>().await;
let mut chain = validator.new_chain().await;
let initial_state = 42u64;
let application_id = chain
.create_application(module_id, (), initial_state, vec![])
.await;
let increment = 15u64;
chain
.add_block(|block| {
block.with_operation(application_id, increment);
})
.await;
let final_value = initial_state + increment;
let QueryOutcome { response, .. } =
chain.graphql_query(application_id, "query { value }").await;
let state_value = response["value"].as_u64().expect("Failed to get the u64");
assert_eq!(state_value, final_value);
}