In the previous BlockSec article on Rust smart contract development, we introduced how to define the contract status for the contract StatusMessage and implemented different methods for the contract. In this issue, we will continue to narrate based on the contract, introduce in detail the method of writing unit test cases, and test the contract locally.1. Prepare the unit test environment
1 #[cfg(not(target_arch = "wasm32"))]
2 #[cfg(test)]
3 mod tests {
4 use super::*;
5 use near_sdk::MockedBlockchain;
6 use near_sdk::{testing_env, VMContext};
7
8 ...
9}
To write unit tests, first we need to add the following code to src/lib.rs to set up the environment for unit tests:"wasm32"))]。
In lines 1-3 of the above code, we added the tests submodule for StatusMessage (using the mod keyword to declare the new module), and marked the cfg attribute macro #[cfg(test)] before the code snippet of the module . Also, since Rust's native unit tests don't need to get Wasm code, the test module can be configured with Rust compilation conditions #[cfg(not(target_arch =
Lines 4-6 of the code import the relevant dependencies of the contract test environment from near_sdk (NEAR's software development kit). Specifically, in each line of the code, the usage of the use keyword is similar to the import used by python language code when importing other dependent modules. A use declaration creates one or more local name bindings that are synonymous with other paths, i.e. the use keyword is often used to declare the paths required to refer to a module item, and these declarations may usually appear at the top of a Rust module or code block .
In line 4, the super keyword can be used to access the parent module StatusMessage from the current module, enabling access to the functions and methods defined in the parent module, such as the method functions set_status and get_status we defined for the StatusMessage contract before. Line 5 uses the use keyword to refer to the mock blockchain MockedBlockchain support module provided by nearsdk, which can be used for smart contract testing. Line 6 introduces the contract test execution environment from nearsdk, as well as the support for the context information format of the test environment.
1 fn get_default_context(view_call: bool) -> VMContext {
2 VMContext {
3 current_account_id: "alice_near".to_string(),
4 signer_account_id: "bob_near".to_string(),
5 signer_account_pk: vec!,
6 predecessor_account_id: "carol_near".to_string(),
7 input: vec!,
8 block_index: 0,
9 block_timestamp: 0,
10 account_balance: 0,
11 account_locked_balance: 0,
12 storage_usage: 0,
13 attached_deposit: 0,
14 prepaid_gas: 10u64.pow(18),
15 random_seed: vec!,
16 is_view: view_call,
17 output_data_receivers: vec!,
18 epoch_height: 0,
19 }
20 }
After importing the external dependency modules required to support NEAR smart contract unit tests, we also need to define the following function get_context() in the test module to configure and return the context information required in the test environment: VMContext.
VMContext sets multiple simulations, contract user account information, and context configuration information related to the bottom layer of the blockchain including block height, block timestamp, contract storage usage, etc.
The following first explains several key attribute configurations in VMContext:
current_account_id: The account executing the current contract. signer_account_id: The transaction signer who triggers the execution of the current contract function call. All contract calls are the result of a transaction, and the transaction is signed by an account using its Access Key, which is the signer_account_id. signer_account_pk: Access Key public key (Public Key) used by the transaction signer. predecessor_account_id: When the execution of the contract is a cross-contract call or callback, this attribute refers to the initiator account of the call. When making a single contract internal function call, this value will be consistent with signer_account_id. prepaid_gas: There is a feature when executing contracts in the blockchain, that is, users need to pay a certain transaction execution fee (gas fee). The prepaid_gas here sets the maximum value of Gas that can be deducted when the current transaction contract function is called, and it is added to the current contract call. is_view: The parameter is_view (the type is bool) can set whether the call of the contract function can modify the state data of the contract. If the value is true, the status data of the contract is read-only when the contract function is executed. Conversely, if the value is false, the execution environment of the contract will allow modification of the contract data. The content and usage of the remaining attributes in VMContext will be described in detail in subsequent articles.
near_sdk::env::current_account_id()
near_sdk::env::predecessor_account_id()
near_sdk::env::signer_account_pk()
near_sdk::env::input()
near_sdk::env::predecessor_account_id()
When executing a NEAR contract, the program can cooperate with some related APIs provided by the NEAR SDK to read the set context information. For example:
All of the above APIs return the value of context-specific properties, and these APIs can be imported using the use statement described earlier.
After defining the function get_context(), we can write the unit test content one by one in the test module.
2. Unit Test 1
1 #[test]
2 fn set_get_message() {
3 let context = get_default_context(false);
4 testing_env!(context);
5 let mut contract = StatusMessage::default();
6 contract.set_status("hello".to_string());
7 assert_eq!(
8 "hello".to_string(),
9 contract.get_status("bob_near".to_string()).unwrap()
10 );
11 }
Here is the code snippet for unit test 1:
Now we describe the specific way of writing test cases:
On line 1 of the above code snippet, we marked the unit test function with the #[test] macro, indicating that this is the starting point for the unit test. Immediately following line 2 is the declaration of the unit test function set_get_message().
Lines 3-10 of the code are the main test logic inside the unit test function, where the code implementation first calls the previously defined get_context to initialize a context context used in the test environment. In addition, it is worth mentioning that since this unit test needs to write data to the state data of the contract, it is necessary to set parameters for get_context, and set the is_view attribute in the VMContext mentioned above to false, otherwise a panic will be triggered inside the unit test. The test failed.
After setting a reasonable contract execution context, line 4 of the code will use the context VMContext to use the testing_env! macro to initialize a MockedBlockchain instance for smart contract interaction. Line 5 of the code will call StatusMessage::default() defined in the parent module to generate the initialized contract object contract."Hello"In the subsequent code, the test will first call the set_status method defined by the parent module StatusMessage to save the string in the contract status data"assertion failed". Then use get_status to read the piece of data from the contract status data, and compare it with the expected content. Pass this unit test if the content matches each other, fire in this test thread if they don't
type of panic.
The writing method of using assert to verify in unit test is as follows:
assert_eq!(left,The assert!(expression) macro can check the boolean value, and pass the test if and only if the content referred to by the expression expression is true;
assert_ne!(left,right) macros are often used to check whether they are equal, and pass the check if and only if the contents referred to by the left and right expressions are consistent;
right) macros are often used to check whether they are different, and pass the check if and only if the contents referred to by the left and right expressions are different;
3. Unit test 2
1 #[test]
2 fn get_nonexistent_message() {
3 let context = get_default_context(true);
4 testing_env!(context);
5 let contract = StatusMessage::default();
6 assert_eq!(None, contract.get_status("francis.near".to_string()));
7 }
Here is the code snippet for unit test 2:
In the test on line 6, the expression on the right side of assert_eq uses the contract method get_status to try to query the message information corresponding to the StatusMessage contract user francis.near from the contract status data. However, since line 5 of the code only initializes the state of the entire contract, the contract data at this time is empty as a whole, so its return value will be None. Finally, since the result is as expected, the assertion is correct and the unit test can pass.
4. Execute the test case
[dependencies]
near-sdk = "3.1.0"
After writing the above unit tests, we also need to configure the Cargo.toml file of the contract in the StatusMessage Rust project, that is, add the dependency on near-sdk in the [dependencies] section of the file (the version number is 3.1. 0).
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::LookupMap;
use near_sdk::{env, near_bindgen};
At the same time, we also need to import these modules or packages from near_sdk at the beginning of the src/lib.rs file:
$ cargo test --package status-message
After configuring the dependencies of the contract project, we can use cargo to execute all unit test cases. The specific commands are as follows:
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in0.00s
The test will return specific test results:
$ cargo test --package status-message set_get_message
Similarly, we can obtain the results of individual tests:
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in0.00s
Summary and preview of this issue
Summary and preview of this issue