Simulation Tests
Note: simulation tests are being deprecated in favor of the Sandbox. Until the Sandbox is ready, simulation tests are still a useful approach to the types of testing described on this page. Please note, however, that there will not be ongoing development for it.
#
Unit tests vs. simulation testsUnit tests are great for ensuring that functionality works as expected. This might include checking that function get_nth_fibonacci(n: u8)
works as expected, handles invalid input gracefully, etc. Unit tests in smart contracts might similarly test public functions, but can get unruly if there are several calls between accounts. As mentioned in the unit tests section, there is a VMContext
object used by unit tests to mock some aspects of a transaction. One might, for instance, modify the testing context to have the predecessor_account_id
of "bob.near"
. The limits of unit tests become obvious with certain interactions, like transferring tokens. Since "bob.near"
is simply a string and not an account object, there is no way to write a unit test that confirms that Alice sent Bob 6 NEAR (โ). Furthermore, there is no way to write a unit test that executes cross-contract calls.
Simulation tests provide the ability to have (mocked) end-to-end testing that includes cross-contract calls, proper user accounts, access to state, structured execution outcomes, and more. It should be noted that simulation tests are not a one-to-one match with the behavior of nodes, but still offers significantly improved features to unit tests. (The Sandbox aims to offer the same behavior as real nodes, with the ability to patch state and more.)
#
When to use simulation testsYou'll probably want to use simulation tests when:
- there are cross-contract calls
- there are multiple contracts that interact
- there are multiple users whose balance changes
- you're writing something that "feels" like an end-to-end test
#
SetupUnlike unit tests (which often live in the src/lib.rs
file of the contract), simulation tests are conventionally in a separate subdirectory of the contract called tests
. Refer to this folder structure below:
The Cargo.toml
file will need to have two items to run simulation tests.
- The library target's
crate-type
must includerlib
. See the Rust docs for more information about the manifest's library target. See the example below:
- Under the development dependencies, adding the simulation test crate.
The main.rs
file above will contain the simulation tests. These will be run alongside the other tests using the typical test command:
note
You may also run the simpler command cargo test
, but the one above will allow for messages to appear on the terminal, such as logs and print lines. More info here.
#
Comparing an example#
Unit testLet's take a look at a very simple unit test and simulation test that accomplish the same thing. Normally you wouldn't duplicate efforts like this, but it will be informative.
One of the simple examples on the NEAR Examples landing page is the Rust counter. We'll be using snippets from this repository to demonstrate simulation tests.
First, note this unit test that tests the functionality of the increment
method:
The test above sets up the testing context, instantiates the smart contract's struct called Counter
, calls the increment
method, and makes an assertion that a number field on the struct is now 1
.
Let's look at how this might be written with simulation tests. The snippet below is a bit longer as it demonstrates a couple of things worth noting.
#
Simulation testIn the simulation above, we initialize the simulator, getting a root
account. Then the compiled smart contract for the Rust Counter example is deployed to a simulation environment. This environment does not persist between testing and offers a similar environment to how a NEAR network node interacts. Next we see calls to a view and change method (with the view!
and call!
macros) along with an assertion checking the expected value.
Above halfway down the simulation test we'll see another approach to a view and change method that doesn't use macros. (These begin with root.call
and root.view
.) The choice between using the macro or non-macro approach is stylistic. The fungible token example is a great reference for the macro and non-macro approaches to testing.
Please visit the crate reference documentation for details on the available methods with simulation testing.
info
Pitfall: you must compile your contract before running simulation tests. Because sim tests use the Wasm binary, if changes are made to the smart contract code it will not automatically be tested against.
#
Use contract in simulation testinfo
Quirk: behind the scenes, simulation tests will create an import object using Pascal casing. The format is StructnameContract. A number of smart contracts call their primary struct (the one decorated with #[near_bindgen]) Contract
. Hence the Pascal case object becomes ContractContract.
Contracts like this will often assign a different name as shown below:
We'll finish this page with some helpful examples demonstrating various aspects of simulation testing.
#
Helpful snippets#
Create a simulated user account#
Using a custom genesis config#
Working with execution outcomesThis sets the variable res
to be the execution result of a change method. It also demonstrates unwrapping a value from the call. Note that besides unwrap_json
there is also unwrap_borsh
and unwrap_json_value
which may be useful.
#
Failed execution resultsA helpful utility function to check if an execution result has failed. This does not give insight into the error message itself.
#
More detailed failed execution resultWhile not ideal (as it looks for a substring) this is a method to identify a particular error that's thrown.
#
Producing blocks to "move forward" in timeThis example (and particularly the entire test) shows how one might produce blocks using simulation tests. This may be useful if a contract is supposed to act differently based on block height. As mentioned earlier in this page, simulation tests will be deprecated, so the syntax may be a bit odd for this use case.