- 08 Oct 2024
- 4 Minutes to read
- DarkLight
- PDF
Explain Your First Test
- Updated on 08 Oct 2024
- 4 Minutes to read
- DarkLight
- PDF
It's time to focus on testing. Thorough testing is essential to ensuring the proper predictable working of any Scrypto packages we write. You may have noticed an example of this in the Hello template used in several previous sections. It has a test/
directory that holds a lib.rs
file containing two test functions. These demonstrate two ways to test the Hello blueprint and the two main ways to test any Scrypto package. Here we'll explain both and show you how to run the tests.
Contents
Testing Blueprints and Modules
There are two ways to test blueprints and modules, the Ledger Simulator and Test Environment. LedgerSimulator
is better suited for integration testing, while TestEnvironment
is more ideal for unit testing.
Ledger Simulator
The Ledger Simulator is an in-memory ledger simulator. Tests interact with the simulator as a user submitting transactions to the network would. This is great for integration and end-to-end testing.
To test in Scrypto, we import scrypto_test::prelude::*
and the test version of our blueprint. In our case that's the hello
blueprint imported with use hello_test::hello_test
, hello_test
is the package name followed by the blueprint name, appended with _test
for the test version of said blueprint. These are imported at the top of our test file:
use scrypto_test::prelude::*;
use hello_test::hello_test::*;
For testing you need to import the test version of packages, appended with _test
. e.g. to test example_blueprint
in the example_package
package you would import it with:
use example_package::example_blueprint_test::*
To make this import work, we need to add scrypto_test
to the Cargo.toml
file:
[dev-dependencies]
scrypto-test = { version = "1.2.0" }
Where we also need to make sure the test
feature is enabled:
[features]
default = []
test = []
Then we can create our simulated ledger. In our case that's back in the test/lib.rs
file inside the test_hello
function:
#[test]
fn test_hello() {
// Setup the ledger
let mut ledger = LedgerSimulatorBuilder::new().build();
In that environment, we create an account:
// Create an account
let (public_key, _private_key, account) = ledger.new_allocated_account();
We then need the package available in the environment:
// Publish package
let package_address = ledger.compile_and_publish(this_package!());
Once we have the package we can test the instantiate function. This is done by:
Building a manifest with the the ManifestBuilder:
let manifest = ManifestBuilder::new() .lock_fee_from_faucet() .call_function( package_address, "Hello", "instantiate_hello", manifest_args!(), ) .build();
Submitting the manifest to the ledger:
let receipt = ledger.execute_manifest( manifest, vec![NonFungibleGlobalId::from_public_key(&public_key)], );
Checking the manifest receipt to see if it successfully instantiated a new component, then storing the component address for later use if it did:
let component = receipt.expect_commit(true).new_component_addresses()[0];
With the component in our test environment and its address, we can now test the free_token
method. A similar 3 steps are followed, but with a different manifest:
Build a manifest:
let manifest = ManifestBuilder::new() .lock_fee_from_faucet() .call_method(component, "free_token", manifest_args!()) .call_method( account, "deposit_batch", manifest_args!(ManifestExpression::EntireWorktop), ) .build();
Submit the manifest to the ledger:
let receipt = ledger.execute_manifest( manifest, vec![NonFungibleGlobalId::from_public_key(&public_key)], );
Check the manifest receipt to see if it was successful:
receipt.expect_commit_success();
We do not need to check the return value of the free_token
method as we are testing the ledger interaction, not the logic of the method. If the method returns an error, the test will fail. Testing the logic of the method is more easily done with TestEnvironment
.
Test Environment
The Test Environment framework is different to the Ledger Simulator. Instead of interacting with the ledger as a user, tests interact as native blueprints. This removes the need for transaction manifests and opens up some extra options unavailable with LedgerSimulator
. These differences make it better suited for unit testing the logic of a blueprint.
Testing our Hello blueprint with TestEnvironment
is done with the same test import modules:
use scrypto_test::prelude::*;
use hello_test::hello_test::*;
Meaning scrypto-test
is still needed in our Cargo.toml
file's dev-dependencies, with the test
feature enabled:
[dev-dependencies]
scrypto-test = { version = "1.2.0" }
# --snip--
[features]
default = []
test = []
We'll use TestEnvironment
to test the free_token
method output with a AAA testing pattern: Arrange, Act, Assert.
In our test/lib.rs
file, with the modules imported we create a new environment and arrange the conditions for our test by publishing our package and instantiating a new Hello component from it - no manifest required:
// Arrange
let mut env = TestEnvironment::new();
let package_address = PackageFactory::compile_and_publish(this_package!(), &mut env)?;
let mut hello = Hello::instantiate_hello(package_address, &mut env)?;
This allows us to then perform the action we want to test by calling the method:
// Act
let bucket = hello.free_token(&mut env)?;
The method returns whatever it would on ledger; in this case a bucket. We can now check the amount of tokens in the bucket is what we expect with an assertion:
// Assert
let amount = bucket.amount(&mut env)?;
assert_eq!(amount, dec!("1"));
If the assertion is incorrect the test will panic and the test will fail. If the assertion is correct we can return an Ok
(containing an empty value):
Ok(())
If you're wondering about the new syntax, TestEnvironment uses Result
return types for error handling, so we can use the ?
operator to propagate errors up the call stack, and OK to return the function values. In our case we're just returning Ok(())
, with an empty value, to indicate the test passed and propagated errors are handled by the test framework.