diff --git a/docs/development/testing/e2e.md b/docs/development/testing/e2e.md deleted file mode 100644 index 8d28caaa9e..0000000000 --- a/docs/development/testing/e2e.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: "Full E2E" -hide_title: true -slug: /contract-testing/end-to-end-e2e-testing ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -![Testing1 Title Picture](/img/title/testing1.svg) - -# End-to-End (E2E) Tests - -E2E testing enables developers to write a test that will not only test the contract in an -isolated manner; instead the contract will be tested _together_ with all components that -will be involved on-chain – so from end to end. This way of testing resembles closely -how the contract will actually behave in production. - -As part of the test, the contract will be compiled and deployed to a Polkadot SDK node that -is running in the background. ink! offers API functions that enable developers to then -interact with the contract via transactions that they create and submit to the blockchain. - -You as a developer can define assertions on the outcome of their transactions, such as checking -for state mutations, transaction failures or incurred gas costs. - -Your chain configuration will be tested together with the smart contract. And if your -chain has pallets that are involved with the smart contract execution, those will be -part of the test execution as well. - -ink! does not put any requirements on the Polkadot SDK node in the background – for example, -you can run a node that contains a snapshot of a live network. - -## Example - -The following code example illustrates a basic E2E test for the -[flipper example](https://github.com/use-ink/ink-examples/blob/main/flipper/lib.rs). - -```rust -#[ink_e2e::test] -async fn default_works(mut client: Client) -> E2EResult<()> { - // When the function is entered, the contract was already - // built in the background via `cargo contract build`. - // The `client` object exposes an interface to interact - // with the Polkadot SDK node. - - // given - let mut constructor = FlipperRef::new_default(); - - // when - let contract = client - .instantiate("flipper", &ink_e2e::bob(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let call_builder = contract.call_builder::(); - - // then - let get = call_builder.get(); - let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), false)); - - Ok(()) -} -``` - -You can run the above test by going to the `flipper` folder in -[the ink! examples directory](https://github.com/use-ink/ink-examples/tree/main). - -Before you can run the test, you have to install a Polkadot SDK -node with `pallet-revive`. By default, e2e tests require that you install [`ink-node`](https://github.com/use-ink/ink-node). You do not need to run it in the background since the node is started for each test independently. -The easiest way is to -[download a binary from our releases page](https://github.com/use-ink/ink-node/releases) -(Linux and Mac). -Alternatively, you can build the node by yourself. -The build instructions and pre-requisites can be found -[here](https://github.com/use-ink/ink-node?tab=readme-ov-file#build-locally). - -If you want to run any other node with `pallet-revive` you need to change `CONTRACTS_NODE` environment variable: - -```bash -export CONTRACTS_NODE="YOUR_CONTRACTS_NODE_PATH" -``` - -And finally execute the following command to start e2e test execution. - - - - ```bash - cargo contract test --features e2e-tests - ``` - - - ```bash - pop test --e2e - ``` - - diff --git a/docs/development/testing/node-tests.md b/docs/development/testing/node-tests.md new file mode 100644 index 0000000000..25afdc8873 --- /dev/null +++ b/docs/development/testing/node-tests.md @@ -0,0 +1,163 @@ +--- +title: Node Tests +hide_title: true +slug: /contract-testing/node-tests +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +![Testing Title Picture](/img/title/testing1.svg) + +# Node Tests + +Node tests use `#[ink_e2e::test]` and deploy your contract to an actual Substrate node. + +The examples below are based on the [assets-precompile-node contract](https://github.com/use-ink/ink/blob/master/integration-tests/public/assets-precompile-node/lib.rs). + +## Basic Structure + +```rust +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests { + use super::*; + use ink_e2e::{ContractsBackend, alice, bob, assert_ok, assert_noop, assert_last_event}; + + type E2EResult = Result>; + + #[ink_e2e::test] + async fn it_works(mut client: Client) -> E2EResult<()> { + let contract = client + .instantiate("my-contract", &alice(), &mut MyContractRef::new(42)) + .submit() + .await?; + + let call_builder = contract.call_builder::(); + let result = client.call(&alice(), &call_builder.get()).dry_run().await?; + assert_eq!(result.return_value(), 42); + + Ok(()) + } +} +``` + +## Running Tests + + + + ```bash + pop test --e2e + ``` + + + Requires `ink-node`. See [Setup](../../../getting-started/setup.md#ink-node) for installation. + + ```bash + export CONTRACTS_NODE=ink-node + cargo test --features e2e-tests + ``` + + + +## Asserting State + +```rust +#[ink_e2e::test] +async fn balance_of_works(mut client: Client) -> E2EResult<()> { + let asset_id: u32 = 1; + + client.create(&asset_id, &alice(), 1u128).await?; + client.mint_into(&asset_id, &alice(), &alice(), 1000u128).await?; + + let contract = client + .instantiate("assets_precompile_node", &alice(), &mut AssetHubPrecompileRef::new(asset_id)) + .submit() + .await?; + + let call_builder = contract.call_builder::(); + let result = client.call(&alice(), &call_builder.balance_of(alice().address())).dry_run().await?; + assert_eq!(result.return_value(), U256::from(1000)); + + Ok(()) +} +``` + +## Testing Success + +```rust +use ink_e2e::assert_ok; + +#[ink_e2e::test] +async fn transfer_works(mut client: Client) -> E2EResult<()> { + // Setup asset and contract... + + let result = client + .call(&alice(), &call_builder.transfer(bob_addr, 1_000.into())) + .submit() + .await?; + + assert_ok!(&result); + + Ok(()) +} +``` + +## Testing Errors + +```rust +use ink_e2e::assert_noop; + +#[ink_e2e::test] +async fn transfer_fails_insufficient_balance(mut client: Client) -> E2EResult<()> { + // Setup asset and contract... + + let result = client + .call(&alice(), &call_builder.transfer(bob_addr, 1_000_000.into())) + .submit() + .await?; + + assert_noop!(result, "BalanceLow"); + + Ok(()) +} +``` + +## Testing Events + +```rust +use ink_e2e::assert_last_event; + +#[ink_e2e::test] +async fn transfer_emits_event(mut client: Client) -> E2EResult<()> { + // Setup and transfer... + + let result = client + .call(&alice(), &call_builder.transfer(bob_addr, 1_000.into())) + .submit() + .await?; + + assert_ok!(&result); + assert_last_event!( + &result, + Transfer { + from: contract.addr, + to: bob_addr, + value: 1_000.into() + } + ); + + Ok(()) +} +``` + +## Cargo.toml + +```toml +[dev-dependencies] +ink_e2e = { version = "6.0.0-beta.1" } + +[features] +default = ["std"] +std = ["ink/std"] +e2e-tests = [] +``` diff --git a/docs/development/testing/overview.md b/docs/development/testing/overview.md index 9e88d08b74..401c90b17c 100644 --- a/docs/development/testing/overview.md +++ b/docs/development/testing/overview.md @@ -4,17 +4,72 @@ hide_title: true slug: /contract-testing/overview --- -![Testing1 Title Picture](/img/title/testing1.svg) +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -# Testing Strategies +![Testing Title Picture](/img/title/testing1.svg) -ink! supports three different stages of testing: unit, integration -and end-to-end tests. In this chapter we'll explain what the purpose -of each stage is about and how to use it. +# Testing Overview -![ink! Testing Strategies](/img/testing.png) +ink! provides three testing approaches for different needs: -Generally you can think of those three types of testing as a pyramid -with the top being the most elaborate test. The End-to-End (E2E) -tests at the top will test the lower layers of the pyramid as part -of them. +| Aspect | Unit Tests | Runtime Tests | Node Tests | +|--------|-----------|---------------|------------| +| **Runtime interaction** | ❌ No | ✅ Yes | ✅ Yes | +| **Cross-contract calls** | ❌ No | ✅ Yes | ✅ Yes | +| **Precompiles** | ❌ No | ✅ Yes | ✅ Yes | +| **Initial compile** | Fast | Slow (compiles runtime) | Medium | +| **Test execution** | Fastest | Fast (after first compile) | Slower (node spawn) | +| **Rust analyzer** | ✅ Contract | ✅ Contract + Runtime | ✅ Contract | +| **External dependencies** | None | None | Requires ink-node | + +## When to Use What + +``` +Does your contract need to interact with the runtime? +(cross-contract calls, precompiles, pallet access) + +├── NO → Use Unit Tests +│ Fast, simple, test your contract logic in isolation +│ +└── YES → Use Runtime Tests or Node Tests + │ + ├── Need to debug runtime internals? + │ └── Use Runtime Tests + │ Rust analyzer works on runtime code too + │ + ├── Want fast iteration after first compile? + │ └── Use Runtime Tests + │ First compile is slow, then very fast + │ + ├── Want quick setup, no runtime compilation? + │ └── Use Node Tests + │ Spawns ink-node, slightly slower per test + │ + └── Need the most production-like environment? + └── Use Node Tests + Tests against a real node, closest to mainnet +``` + +## Running Tests + + + + ```bash + # Unit tests + pop test + + # E2E tests (runtime and node) + pop test --e2e + ``` + + + ```bash + # Unit tests + cargo test + + # E2E tests (runtime and node) + cargo test --features e2e-tests + ``` + + diff --git a/docs/development/testing/runtime-tests.md b/docs/development/testing/runtime-tests.md new file mode 100644 index 0000000000..554b6c57a9 --- /dev/null +++ b/docs/development/testing/runtime-tests.md @@ -0,0 +1,164 @@ +--- +title: Runtime Tests +hide_title: true +slug: /contract-testing/runtime-tests +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +![Testing Title Picture](/img/title/testing1.svg) + +# Runtime Tests + +Runtime tests use `#[ink_e2e::test(runtime)]` and compile the Polkadot SDK runtime in-process alongside your tests. + +The examples below are based on the [assets-precompile contract](https://github.com/use-ink/ink/blob/master/integration-tests/public/assets-precompile/lib.rs). + +## Basic Structure + +```rust +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test(runtime)] + async fn it_works(mut client: Client) -> E2EResult<()> { + let contract = client + .instantiate("my-contract", &alice(), &mut MyContractRef::new(42)) + .submit() + .await?; + + let call_builder = contract.call_builder::(); + let result = client.call(&alice(), &call_builder.get()).dry_run().await?; + assert_eq!(result.return_value(), 42); + + Ok(()) + } +} +``` + +## Running Tests + + + + ```bash + pop test --e2e + ``` + + + ```bash + cargo test --features e2e-tests + ``` + + + +## Runtime API + +Use `client.runtime()` to interact directly with runtime pallets: + +| Function | Description | +|----------|-------------| +| **Assets** | | +| `create(&asset_id, &admin, min_balance)` | Create a new asset | +| `mint_into(&asset_id, &account, amount)` | Mint tokens to an account | +| `balance_of(&asset_id, &account)` | Get token balance | +| `allowance(&asset_id, &owner, &spender)` | Get spending allowance | +| `approve(&asset_id, &owner, &spender, amount)` | Approve spending | +| **Accounts** | | +| `map_account(&account)` | Map account for EVM compatibility | + +## Asserting State + +```rust +#[ink_e2e::test(runtime)] +async fn balance_of_works(mut client: Client) -> E2EResult<()> { + let asset_id: u32 = 1; + + client.runtime().create(&asset_id, &alice(), 1u128)?; + client.runtime().mint_into(&asset_id, &alice(), 1000u128)?; + + let balance = client.runtime().balance_of(&asset_id, &alice()); + assert_eq!(balance, 1000u128); + + Ok(()) +} +``` + +## Testing Success + +```rust +use ink_runtime::assert_ok; + +#[ink_e2e::test(runtime)] +async fn transfer_works(mut client: Client) -> E2EResult<()> { + // Setup asset and contract... + + let result = client + .call(&alice(), &call_builder.transfer(bob_addr, 100.into())) + .submit() + .await?; + + assert_ok!(result); + + Ok(()) +} +``` + +## Testing Errors + +```rust +use ink_runtime::assert_noop; + +#[ink_e2e::test(runtime)] +async fn transfer_fails_insufficient_balance(mut client: Client) -> E2EResult<()> { + // Setup asset and contract... + + let result = client + .call(&alice(), &call_builder.transfer(bob_addr, 1_000_000.into())) + .submit() + .await?; + + assert_noop!(result, "BalanceLow"); + + Ok(()) +} +``` + +## Testing Events + +```rust +use ink_runtime::assert_last_event; + +#[ink_e2e::test(runtime)] +async fn transfer_emits_event(mut client: Client) -> E2EResult<()> { + // Setup and transfer... + + assert_last_event!( + &mut client, + Transfer { + from: contract_addr, + to: bob_addr, + value: 100.into() + } + ); + + Ok(()) +} +``` + +## Cargo.toml + +```toml +[dev-dependencies] +ink_e2e = { version = "6.0.0-beta.1" } +ink_runtime = { git = "https://github.com/use-ink/ink.git", branch = "6.0.0-beta.1" } + +[features] +default = ["std"] +std = ["ink/std"] +e2e-tests = [] +``` diff --git a/docs/development/testing/sandbox.md b/docs/development/testing/sandbox.md deleted file mode 100644 index cf05d5532e..0000000000 --- a/docs/development/testing/sandbox.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -title: "Sandboxed E2E" -hide_title: true -slug: /contract-testing/drink ---- - -![Drink Title Picture](/img/title/drink.svg) - -:::note -TODO This page is still WIP for ink! v6. -::: - -# DRink! - -Apart from the core ink! testing framework, we also provide the [DRink!](https://github.com/use-ink/drink) library. -It offers an intermediate solution between integration and E2E testing. - -Intuitively, DRink! maintains a full in-memory blockchain state and allows you to interact with it directly. -This gives a notably robust methodology in contrast to the off-chain engine facilitated by the `ink::test` macro. -Nevertheless, it is essential to note that: -- Drink! directly depends on Polkadot-SDK pallets (including `pallet-revive`, `pallet-balances` e.t.c), - which incurs significant compile-time costs that are not suffered by other testing strategies (including E2E tests). -- The absence of the entire node layer makes the environment less realistic compared to the comprehensive setup employed in the E2E tests. - -## Comparison to the other testing strategies - -To better assess the tradeoffs between DRink! and other testing methods, -it is crucial to gain a deeper understanding of the consequences that its unique design entails -for different use-cases. - -1. **In-process execution speed at the cost of compilation time:** -since DRink! doesn't spawn any node or background process, everything happens locally, within the testing thread. -This means that the execution can be synchronous, with in-process execution performance comparable to -unit or integration testing, and significantly faster than E2E tests. -However, this comes at the cost of significantly increased compilation time -compared to all other testing strategies (including E2E tests), -because Drink! directly depends on (and hences compiles) Polkadot-SDK pallets including `pallet-revive`, -which notably includes a significant number of [REVM][revm]-only dependencies that aren't relevant -to ink! contracts, but which still have to be compiled for every contract. -Also, there is no block production or finalization delay for Drink!, -which can be a noticeable factor in the E2E tests depending on the target node. -However, it's notable that unlike production nodes, development focused nodes like [`ink-node`][ink-node] -use instant/manual seal to produce blocks immediately and skip consensus related processing, -thus minimizing their execution overhead for the same use-cases. - -2. **Testing multiple contracts:** -since we are working with a full blockchain state, we can perform any interaction with the contracts, -which includes working with mutliple contracts at the same time. -Of course, this is the same as in the E2E tests, but it is not possible in either the unit or integration tests. - -3. **Working with arbitrary runtimes:** -similarly to the E2E tests, where we can spawn any node with customized runtime -(Polkadot's term for the state transition function), -in DRink! tests we can work with any blockchain runtime we want. - -4. **Full control over runtime state:** -we hold the state of the blockchain and exercise full control over it, so we can easily manipulate it however we want. -This covers manipulating block number, timestamp, account balances, e.t.c. -Some of these are also possible in the E2E tests, but usually they require more effort or overhead. - -5. **Powerful features:** -thanks to the unique design of DRink!, we can easily take advantage of some powerful, -not available in other testing strategies, features: - - **contract mocking:** you can mock any contract or message you want, specifying the default behavior or the exact return value. - - **enhanced debugging and call tracing:** you can get more insights into the contract execution process, like stack trace, debug buffers and more. - -Nevertheless, we'll reemphasize some of DRink!'s drawbacks as well: - -1. **Increased compile-times due to Polkadot-SDK dependencies**: -as detailed previously, this is one of the most significant trade-offs of Drink!. -This significant compile-time comes from Drink!'s direct dependence on Polkadot-SDK pallets, -including `pallet-revive`, which notably also requires compilation of a significant number -of [REVM][revm]-only dependencies that aren't relevant to ink! contracts, -but which still have to be compiled for every contract. - -2. **Artificial, isolated environment:** -this is another major trade-off of DRink!. -It might give a false sense of security, while in the real environment, the contract could behave differently. -The discrepancy can be mitigated by a careful and precise simulation and setup of the environment, but it is still a factor to consider. - -3. **No node layer:** -since we don't spawn any node, we don't have access to the node layer, -which means that we can't test any node-related functionality, like RPC calls, block production, etc. - -4. **No typed contract API:** currently, DRink! works with string-encoded arguments and values, -which means that we lose the type safety and convenience that was present in the other testing frameworks. -Fortunately, this is going to change soon, as there is an ongoing effort to integrate it with [ink-wrapper] library. - -[revm]: https://crates.io/crates/revm -[ink-node]: https://github.com/use-ink/ink-node -[ink-wrapper]: https://github.com/Cardinal-Cryptography/ink-wrapper - -## When to use `DRink!`? - -DRink! is a good choice for the development phase of your project, -if your test cases require complex state orchestration, -or some unique Drink! feature that isn't available in other testing strategies. -For these cases, DRink! offers a versatile, yet highly efficient testing environment. - -However, you must not forget that it is not a replacement for the E2E tests, -which should be run before the deployment to the production network, as well as in your CI/CD pipelines. - -DRink! also comes in handy when you need to: - - mock some parts of your contract suite - - debug the execution process - - launch long-running simulations, that would normally take a lot of time when relying on a real block-time - -## How to use DRink!? - -There are three ways to use DRink!: - -### Directly as a library - -This way you gain access to full DRink! power in your test suites. - -`drink` library is continuously published to [crates.io](https://crates.io/crates/drink), so you can use it in your project with either `cargo add drink` or by adding the following line to your `Cargo.toml`: -```toml -drink = { version = "0.8" } -``` - -Then, you can write your tests like this: -```rust -#[cfg(test)] -mod tests { - /// This will take care of building all contract dependencies in the compilation phase and gather all contract - /// bundles (metadata and the compiled code) into a single registry. - #[drink::contract_bundle_provider] - enum BundleProvider {} - - /// Within `drink::test` macro, you are provided with a `session` object, which is a wrapper around the - /// blockchain state. You can use it to deploy contracts, call their methods, and more. - #[drink::test] - fn deploy_and_call_a_contract(mut session: Session) -> Result<(), Box> { - let result: bool = session - .deploy_bundle_and(BundleProvider::local(), "new", &["true"], NO_SALT, NO_ENDOWMENT)? - .call_and("flip", NO_ARGS, NO_ENDOWMENT)? - .call_and("flip", NO_ARGS, NO_ENDOWMENT)? - .call_and("flip", NO_ARGS, NO_ENDOWMENT)? - .call("get", NO_ARGS, NO_ENDOWMENT)??; - assert_eq!(result, false); - } -} -``` - -You can check some helpful and verbose examples [here](https://github.com/inkdevhub/drink/tree/main/examples), including the [**quick start guide**](https://github.com/inkdevhub/drink/tree/main/examples/quick-start-with-drink). - -### As an alternative backend to ink!'s E2E testing framework - -DRink! is already integrated with the ink! framework and can be used as a drop-in replacement for the standard E2E testing environment. - -Import `ink_sandbox` in your Cargo.toml: -```toml -ink_sandbox = { git = "https://github.com/use-ink/ink", branch = "6.0.0-beta.1" } -``` - -And just use corresponding argument in the test macro: -```rust -#[ink_sandbox::test(backend(runtime_only( - sandbox = sandbox_runtime::ContractCallerSandbox, - client = ink_sandbox::SandboxClient -)))] -``` -to your test function and you have just switched from E2E testcase to DRink!-based one, that doesn't use any running node in the background! - -For a full example check out [ink! repository](https://github.com/use-ink/ink-examples/tree/main/e2e-runtime-only-backend). - -### With a command line tool - -We provide a CLI which puts DRink! behind friendly TUI. -For more details, consult [its README](https://github.com/inkdevhub/drink/blob/main/drink-cli/README.md). - -Similarly to `drink` library, `drink-cli` is published to [crates.io](https://crates.io/crates/drink-cli) as well. -You can install it with: -```shell -cargo install drink-cli -``` diff --git a/docs/development/testing/unit-integration.md b/docs/development/testing/unit-integration.md deleted file mode 100644 index 01401ba51e..0000000000 --- a/docs/development/testing/unit-integration.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Unit & Integration -hide_title: true -slug: /contract-testing/unit-integration-tests ---- - -![Testing1 Title Picture](/img/title/testing1.svg) - -# Tests - -On this page we lay out the different use-cases for unit vs. integration tests. - -## Unit Tests - -Testing contracts off-chain is done by `cargo contract test` and users can simply use the standard Rust -routines of creating unit test modules within the ink! project: - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn my_test() { ... } -} -``` - -Test instances of contracts can be created with something like: - -```rust -let contract = MyContract::my_constructor(a, b); -``` - -Messages can simply be called on the returned instance as if `MyContract::my_constructor` returns a -`Self` instance. - -See the [flipper example](https://github.com/use-ink/ink-examples/blob/main/flipper/lib.rs). - -## Integration Tests - -For integration tests, the test is annotated with our `#[ink::test]` -attribute instead of `#[test]`. This attribute denotes that -the test is then executed in a simulated, mocked blockchain environment. -Here functions are available to influence how the test environment -is configured (e.g. setting a specified balance of an account to -simulate how a contract would behave when interacting with it). - -If you annotate a test with the `#[ink::test]` attribute it -will be executed in a simulated environment, similar to as it -would be run on-chain. -You then have fine-grained control over how a contract is called; -for example you can influence the block advancement, the value transferred to it, -by which account it is called, which storage it is run with, etc.. - -See the [`examples/erc20`](https://github.com/use-ink/ink-examples/blob/main/erc20/lib.rs) contract on how to utilize those or [the documentation](https://use-ink.github.io/ink/ink/attr.test.html) for details. - -At the moment there are some known limitations to our off-chain environment, -and we are working on making it behave as close to the real chain environment -as possible. - -:::note -One limitation of the off-chain testing framework is that it -currently only supports a `DefaultEnvironment`. - -See [here](../../advanced/environment.md) for an explanation of what an environment is. -::: - -### Example - -```rust -#[cfg(test)] -mod tests { - // Conventional unit test that works with assertions. - #[ink::test] - fn test1() { - // test code comes here as usual - } - - // Conventional unit test that returns some Result. - // The test code can make use of operator-`?`. - #[ink::test] - fn test2() -> Result<(), ink::env::Error> { - // test code that returns a Rust Result type - } -} -``` - -## How do you find out if a test requires the off-chain environment? - -If the test recursively uses or invokes methods that call a function defined -in `self.env()` or `Self::env()`. - -An example is the following: - -```rust -let caller: AccountId = self.env().caller(); -``` diff --git a/docs/development/testing/unit-tests.md b/docs/development/testing/unit-tests.md new file mode 100644 index 0000000000..6d0200d86a --- /dev/null +++ b/docs/development/testing/unit-tests.md @@ -0,0 +1,136 @@ +--- +title: Unit Tests +hide_title: true +slug: /contract-testing/unit-tests +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +![Testing Title Picture](/img/title/testing1.svg) + +# Unit Tests + +Unit tests use the `#[ink::test]` attribute and run your contract logic in a simulated environment. + +The examples below are based on the [ERC20 contract](https://github.com/use-ink/ink-examples/blob/main/erc20/lib.rs). + +## Basic Structure + +```rust +#[cfg(test)] +mod tests { + use super::*; + use ink::env::test::*; + + #[ink::test] + fn new_works() { + let erc20 = Erc20::new(100.into()); + assert_eq!(erc20.total_supply(), 100.into()); + } +} +``` + +## Running Tests + + + + ```bash + pop test + ``` + + + ```bash + cargo test + ``` + + + +## Test Environment API + +All test utilities live in `ink::env::test`: + +| Function | Description | +|----------|-------------| +| **Accounts** | | +| `default_accounts()` | Pre-funded test accounts (alice, bob, charlie, dave, eve, ferdie) | +| `set_caller(address)` | Set the caller for subsequent calls | +| `set_callee(address)` | Set the contract's own address | +| **Balances** | | +| `set_contract_balance(address, balance)` | Set a contract's balance | +| `set_value_transferred(value)` | Set the value sent with the call | +| **Block Context** | | +| `set_block_timestamp(timestamp)` | Set the current block timestamp | +| `set_block_number(number)` | Set the current block number | +| `advance_block()` | Advance to the next block | +| **Events** | | +| `recorded_events()` | Get all events emitted during the test | + +## Asserting State + +```rust +#[ink::test] +fn balance_of_works() { + let accounts = default_accounts(); + set_caller(accounts.alice); + + let erc20 = Erc20::new(100.into()); + + assert_eq!(erc20.balance_of(accounts.alice), 100.into()); + assert_eq!(erc20.balance_of(accounts.bob), 0.into()); +} +``` + +## Testing Success + +```rust +#[ink::test] +fn transfer_works() { + let accounts = default_accounts(); + set_caller(accounts.alice); + + let mut erc20 = Erc20::new(100.into()); + + assert_eq!(erc20.transfer(accounts.bob, 10.into()), Ok(())); + assert_eq!(erc20.balance_of(accounts.bob), 10.into()); +} +``` + +## Testing Errors + +```rust +#[ink::test] +fn transfer_fails_insufficient_balance() { + let accounts = default_accounts(); + set_caller(accounts.bob); // Bob has no tokens + + let mut erc20 = Erc20::new(100.into()); + + assert_eq!( + erc20.transfer(accounts.alice, 10.into()), + Err(Error::InsufficientBalance) + ); +} +``` + +## Testing Events + +```rust +#[ink::test] +fn transfer_emits_event() { + let accounts = default_accounts(); + set_caller(accounts.alice); + + let mut erc20 = Erc20::new(100.into()); + erc20.transfer(accounts.bob, 10.into()).unwrap(); + + let events = recorded_events(); + assert_eq!(events.len(), 2); // Constructor + transfer + + // Decode and verify + let decoded = ::decode(&mut &events[1].data[..]).unwrap(); + assert_eq!(decoded.from, Some(accounts.alice)); + assert_eq!(decoded.to, Some(accounts.bob)); + assert_eq!(decoded.value, 10.into()); +} +``` diff --git a/sidebars.js b/sidebars.js index 5c782b7f18..d5663a562d 100644 --- a/sidebars.js +++ b/sidebars.js @@ -61,9 +61,9 @@ module.exports = { label: "Testing", items: [ "development/testing/overview", - "development/testing/unit-integration", - "development/testing/e2e", - "development/testing/sandbox", + "development/testing/unit-tests", + "development/testing/runtime-tests", + "development/testing/node-tests", // "development/testing/testing-with-live-state" ] },