scrypto_test/
lib.rs

1//! This crate is an implementation of for Scrypto-Test, a unit testing framework for Scrypto that
2//! follows an invocation-based approach instead of a transaction-based approach to testing,
3//! allowing Scrypto developers to write tests that look and feel like Scrypto. Scrypto-Test is not
4//! a replacement for transaction-based tests offered by the LedgerSimulator, it would just be an
5//! addition and another way for Scrypto developers to test their code where Scrypto-Test could be
6//! classified as a unit-testing framework while the LedgerSimulator could be classified as an
7//! integration-testing framework.
8//!
9//! # Why
10//!
11//! We already have a way to test Scrypto blueprints in the from of the scrypto_test::prelude::LedgerSimulator
12//! which is essentially an in-memory ledger that we can run transactions against, get back
13//! transaction receipts, and determine-based on the TransactionReceipt-if the behavior of the
14//! blueprint or component is as we expect or not. This approach is tried and tested and has been
15//! proven to work as evident by the hundreds and thousands of tests in the radix_engine_tests crate
16//! that make it clear that the transaction-based approach works. However, it has a number of
17//! issues, especially when we think about the target audience of our testing framework: DeFi
18//! developers.
19//!
20//! In the current (transaction-based) model, there is a lot of boilerplate code involved to write
21//! what should be a straightforward test. As an example, to test that a contribution of X and Y
22//! resources to Radiswap results in Z pool units minted the test author needs to:
23//!
24//! * Create these two resources to use for testing.
25//! * Decide on whether these resources should just be mintable on demand or if the supply of these
26//!   resources should be stored in some account that will be used in the test to withdraw from.
27//! * If an account will hold those resources, then the author needs to create that account.
28//! * Split out the instructions into multiple manifests as needed such as in cases where one
29//!   instruction depends on the output of a previous instruction.
30//! * Ensure that the execution of the previous transactions did indeed succeed and extract out the
31//!   information required from receipts either through the worktop changes, balance changes, or by
32//!   other means.
33//! * Manage and ensure that the worktop by the end of the transaction is empty of all resources and
34//!   that the accounts that the resources will be deposited into sign the transaction.
35//!
36//! Most if not all of the items listed above are not core to what the developer wishes to test,
37//! recall that they wished to test whether a contribution of X and Y resources returns Z pool
38//! units. However, they spent a majority of their time thinking about completely different
39//! problems. Thus, there is not only a large amount of boilerplate code, but there is also large
40//! mental overhead for a test should be conceptually easy and simple to write. As you can see from
41//! the description above, most of the time that is spent writing tests is **not spent writing
42//! tests**, but spend initializing and creating the environment and managing side effects just to
43//! then write a simple test in the form of a method call to some node id.
44//!
45//! # Scrypto-Test Model
46//!
47//! This model differs from the transaction based model of writing tests in that we do not have
48//! transaction instructions, processor, and worktop at all. In fact, **nothing** related to
49//! transactions exists in this model. Instead, there exists a [`TestEnvironment`] struct that each
50//! test can instantiate instances of. Each [`TestEnvironment`] instance has a substate store,
51//! track, and kernel. On top of that, [`TestEnvironment`] implements the [`SystemApi`] trait. The
52//! [`TestEnvironment`] can be looked at as self-contained Radix Engine that’s exposed through the
53//! [`SystemApi`] and that has some other high-level helper methods as it contains all of the layers
54//! of the engine. This means that:
55//!
56//! * A [`TestEnvironment`] instance is a self-contained instance of the Radix Engine and Kernel
57//!   which are exposed through the [`SystemApi`].
58//! * Since [`TestEnvironment`] implements the [`SystemApi`] it can be used as a substitute to
59//!   ScryptoEnv from Scrypto and the SystemService from native. This means that the simple
60//!   interface seen in the radix_native_sdk crate can be used within tests.
61//! * If each test has it’s own [`TestEnvironment`] instance (they instantiate that themselves if
62//!   they need it), then tests have no shared dependencies and are isolated.
63//! * The biggest struggle with the transaction-based model was around dealing with transient nodes.
64//!   More specifically, if we wanted to make sure that bucket X returned from some invocation
65//!   contained Y resources, how would we do that? Unfortunately, there was no easy way to do it. In
66//!   this model, if we make an invocation and get a Bucket back, there is no worktop for the bucket
67//!   to go into, we have a proper Bucket object that we can call amount() on and assert against.
68//!   Thus, this approach makes it easier to have assertions around transient nodes.
69//!
70//! Once the [`TestEnvironment`] has been instantiated we would get a Kernel with two Call Frames:
71//! 1. **The Root Call Frame:** We have a root Call Frame to be consistent with how other parts of
72//!    the stack use the kernel where there is always a root Call Frame. After the instantiation is
73//!    complete, the root callframe is pushed onto the stack of previous Call Frames the kernel has.
74//! 2. **The Test Call Frame:** This is the Call Frame that is used for all of the invocation that
75//!    will be made throughout the test. This Call Frame functions exactly like any other Call
76//!    Frame, it can own nodes, get messages from other Call Frames, and so on. As an example, say
77//!    we invoke a method on some node that returns a Bucket, this Bucket is now owned and visible
78//!    to this Call Frame. We are now able to call methods such as resource_address() and amount()
79//!    on this Bucket since it’s a node we own and the [`TestEnvironment`] has a Heap.
80//!
81//! [`SystemApi`]: crate::prelude::SystemApi
82//! [`TestEnvironment`]: crate::prelude::TestEnvironment
83
84pub mod environment;
85pub mod ledger_simulator;
86pub mod prelude;
87pub mod sdk;
88pub mod utils;
89
90#[macro_export]
91macro_rules! this_package {
92    () => {
93        env!("CARGO_MANIFEST_DIR")
94    };
95}
96
97/// Includes the WASM file of a Scrypto package.
98///
99/// Notes:
100/// * This macro will NOT compile the package;
101/// * The binary name is normally the package name with `-` replaced with `_`.
102///
103/// # Example
104/// ```ignore
105/// # // Ignoring because of include_code!
106/// use scrypto::prelude::*;
107///
108/// // This package
109/// let wasm1 = include_code!("bin_name");
110///
111/// // Another package
112/// let wasm2 = include_code!("/path/to/package", "bin_name");
113/// ```
114#[macro_export]
115macro_rules! include_code {
116    ($bin_name: expr) => {
117        include_bytes!(concat!(
118            env!("CARGO_MANIFEST_DIR"),
119            "/target/wasm32-unknown-unknown/release/",
120            $bin_name,
121            ".wasm"
122        ))
123    };
124    ($package_dir: expr, $bin_name: expr) => {
125        include_bytes!(concat!(
126            $package_dir,
127            "/target/wasm32-unknown-unknown/release/",
128            $bin_name,
129            ".wasm"
130        ))
131    };
132}
133
134/// Includes the schema file of a Scrypto package.
135///
136/// Notes:
137/// * This macro will NOT compile the package;
138/// * The binary name is normally the package name with `-` replaced with `_`.
139///
140/// # Example
141/// ```ignore
142/// # // Including because of include_schema!(..)
143/// use scrypto::prelude::*;
144///
145/// // This package
146/// let schema1 = include_schema!("bin_name");
147///
148/// // Another package
149/// let schema2 = include_schema!("/path/to/package", "bin_name");
150/// ```
151#[macro_export]
152macro_rules! include_schema {
153    ($bin_name: expr) => {
154        include_bytes!(concat!(
155            env!("CARGO_MANIFEST_DIR"),
156            "/target/wasm32-unknown-unknown/release/",
157            $bin_name,
158            ".rpd"
159        ))
160    };
161    ($package_dir: expr, $bin_name: expr) => {
162        include_bytes!(concat!(
163            $package_dir,
164            "/target/wasm32-unknown-unknown/release/",
165            $bin_name,
166            ".rpd"
167        ))
168    };
169}