Writing tests in Sylvia
As a reminder, the file structure of the counter project written in Sylvia is shown below. In
this chapter, you will be writing tests for this smart contract. All tests will be placed in a file
named test_counter.rs
, highlighted in line 12.
.
├── Cargo.toml
├── coverage.sh
├── src
│ ├── contract.rs
│ ├── lib.rs
│ └── msg.rs
└── tests
├── mod.rs
└── multitest
├── mod.rs
└── test_counter.rs
If you're in a hurry, you can find the final version of the
test_counter.rs
file at the end of this chapter.
Imports
At the very beginning of the test_counter.rs
file, the necessary imports for implementing all test
cases are included:
- structures generated by Sylvia
(
CodeId
,CounterContractProxy
), - initialization message for the contract (
CounterInitMsg
), - a trait for converting a string into address (
IntoAddr
), - and the Sylvia app (chain) struct that simulates the real-life blockchain
(
sylvia::multitest::App
).
Just copy and paste the code presented below to your test_counter.rs
file:
use counter::contract::sv::mt::{CodeId, CounterContractProxy};
use counter::msg::CounterInitMsg;
use sylvia::cw_multi_test::IntoAddr;
use sylvia::multitest::App;
Testing counter initialization
There are two ways to initialize the counter contact:
- with zero value,
- with specific value in range [0..255].
Testing initialization with zero
This first test verifies that the counter contract can be instantiated with an initial value of zero
using the CounterInitMsg::Zero
message. It ensures that the contract correctly stores the
initial value of zero, and that the query mechanism returns the expected counter value.
It is an example of a well-structured test case with clear setup, execution, and validation steps.
#[test]
fn instantiating_with_zero_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Zero)
.call(&owner)
.unwrap();
assert_eq!(0, contract.value().unwrap().value);
}
Let's take a closer look at this test's flow.
Chain initialization
Creating an instance of the App
simulates starting a blockchain. There are multiple ways to
instantiate the App
, but the simplest way, shown below, uses the default
function.
let app = App::default();
Storing contract on chain
The next step is to store the contract's code on the chain. The CodeId
struct generated by
Sylvia for the counter contract provides a store_code
function for this purpose.
store_code
returns a handle to the contract assigned to code_id
. This
code_id
is used to reference the stored contract later in test.
let code_id = CodeId::store_code(&app);
Defining the contract owner
Every instance of the contract has an owner represented as an address. In this test the string
"owner"
is converted to Bech32 address recognized by the blockchain.
let owner = "owner".into_addr();
Contract instantiation
The contract is to be instantiated using instantiate
function provided by code_id
with the initialization message variant CounterInitMsg::Zero
. instantiate
function
returns an
InstantiateProxy
generated by Sylvia, which in turn provides a function call
that triggers the instantiation
of the contract. The call
function gets a single argument being the address of the owner of
the contract instance.
The unwrap()
function is used to handle the result of instantiating the contract, ensuring
the test fails if an error occurs. call
function returns a proxy to contract instance, used
later in this test.
let contract = code_id
.instantiate(CounterInitMsg::Zero)
.call(&owner)
.unwrap();
Querying the contract
After the contract is instantiated, this test queries the contract instance using the
value()
function, which is a method in the
CounterContractProxy
generated by Sylvia from CounterContract
impl
block. Using Sylvia framework,
querying a contract is as simple as calling a function on the contract instance. The result from the
value()
function is simply unwrapped and resolves to CounterResponse
which in turn
holds the current value of the counter in the public field named value
.
assert_eq!(0, contract.value().unwrap().value);
Asserting the value
The final step is to assert that the value returned by the contract is 0
. If the value were
non-zero, the test would fail.
assert_eq!(0, contract.value().unwrap().value);
Running tests
Having the first test ready, let's run it:
cargo test
The expected output should look like the example shown below. Note that only the results of integration tests are shown. For brevity, the results of unit tests and documentation tests are omitted, as we are focusing only on integration tests in this example.
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 1 test
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Now, let's run all tests using cargo-nextest (opens in a new tab):
cargo nextest run
Similarly, one test passes:
Finished `test` profile [unoptimized + debuginfo] target(s) in 27.67s
Starting 1 test across 2 binaries (run ID: b2a72818-8d25-4194-9333-3af93c360132, nextest profile: default)
PASS [ 0.004s] counter::mod multitest::test_counter::instantiating_with_zero_should_work
------------
Summary [ 0.004s] 1 test run: 1 passed, 0 skipped
For brevity, in the upcoming test cases in this chapter, we will skip running tests using
cargo-nextest (opens in a new tab). However, you can always run all tests by typing cargo nextest run
.
Code coverage
Let's check the code coverage after adding the first test:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 8/22
||
36.36% coverage, 8/22 lines covered
First test covered almost 40% of the code in the counter smart contract. The detailed code coverage report (similar to the one generated by Tarpaulin) is shown below.
use crate::msg::{CounterInitMsg, CounterResponse};
use cosmwasm_std::{Response, StdResult};
use cw_storage_plus::Item;
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
pub struct CounterContract {
pub count: Item<u8>,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new("count"),
}
}
#[sv::msg(instantiate)]
fn init(&self, ctx: InstantiateCtx, msg: CounterInitMsg) -> StdResult<Response> {
match msg {
CounterInitMsg::Zero => self.count.save(ctx.deps.storage, &0)?,
CounterInitMsg::Set(value) => self.count.save(ctx.deps.storage, &value)?,
}
Ok(Response::new())
}
#[sv::msg(exec)]
fn inc(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_add(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn dec(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_sub(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn set(&self, ctx: ExecCtx, value: u8) -> StdResult<Response> {
self.count.save(ctx.deps.storage, &value)?;
Ok(Response::new())
}
#[sv::msg(query)]
fn value(&self, ctx: QueryCtx) -> StdResult<CounterResponse> {
let value = self.count.load(ctx.deps.storage)?;
Ok(CounterResponse { value })
}
}
The code coverage report reflects exactly the scope of the first test. The init()
and
value()
functions of the contract were executed. There is still one more message variant
(line 24) to be tested during contract instantiation and this will be covered in the next test. The
functions inc()
, dec()
and set()
were not called in this test and still
shine red
in coverage report.
Testing initialization with a specific value
The second test verifies the initialization of the counter contract using a specific value that
must be in range [0..255]. For the purpose of this example, let say it will be 12
.
Initialization with a specific value is done using the CounterInitMsg::Set
message.
#[test]
fn instantiating_with_value_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(12))
.call(&owner)
.unwrap();
assert_eq!(12, contract.value().unwrap().value);
}
Except for the message used to instantiate the contract and the value assertion, all steps in this test are the same as in the previous one, let's summarize them shortly:
Contract instantiation
This time the contract is instantiated using CounterInitMsg::Set
message passed to
instantiate
function provided by code_id
:
let contract = code_id
.instantiate(CounterInitMsg::Set(12))
.call(&owner)
.unwrap();
Asserting the value
The expected value of the counter is 12
in this test.
assert_eq!(12, contract.value().unwrap().value);
Running tests
To execute all test type in terminal:
cargo test
The expected output should be similar to the one shown below. Both tests pass.
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 2 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
To make sure that the entire init()
function of the counter contract is tested, let's run
the code coverage:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 9/22
||
40.91% coverage, 9/22 lines covered
The detailed code coverage report (similar to the one generated by Tarpaulin) is shown below:
use crate::msg::{CounterInitMsg, CounterResponse};
use cosmwasm_std::{Response, StdResult};
use cw_storage_plus::Item;
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
pub struct CounterContract {
pub count: Item<u8>,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new("count"),
}
}
#[sv::msg(instantiate)]
fn init(&self, ctx: InstantiateCtx, msg: CounterInitMsg) -> StdResult<Response> {
match msg {
CounterInitMsg::Zero => self.count.save(ctx.deps.storage, &0)?,
CounterInitMsg::Set(value) => self.count.save(ctx.deps.storage, &value)?,
}
Ok(Response::new())
}
#[sv::msg(exec)]
fn inc(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_add(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn dec(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_sub(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn set(&self, ctx: ExecCtx, value: u8) -> StdResult<Response> {
self.count.save(ctx.deps.storage, &value)?;
Ok(Response::new())
}
#[sv::msg(query)]
fn value(&self, ctx: QueryCtx) -> StdResult<CounterResponse> {
let value = self.count.load(ctx.deps.storage)?;
Ok(CounterResponse { value })
}
}
As expected, the init()
function is fully tested, including all initialization message
variants. However, the inc()
, dec()
and set()
functions still
shine red
. Let's address this in the next test case.
Testing counter increment
Testing increment by 1
In this test, the contract is instantiated with an initial value of zero, similar to the first test.
However, this time, an increment action is performed by invoking the inc()
method from
generated
CounterContractProxy
trait.
This action is expected to increment the counter by 1
. Finally, the counter's value is queried to
confirm that it has been correctly incremented and now holds the value 1
.
#[test]
fn incrementing_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Zero)
.call(&owner)
.unwrap();
contract.inc().call(&owner).unwrap();
assert_eq!(1, contract.value().unwrap().value);
}
You should already be familiar with the flow of this test; the only difference from the previous
examples is the additional step of invoking the inc()
function.
Executing the increment action
contract.inc().call(&owner).unwrap();
The inc()
function of the
CounterContractProxy
trait
is evaluated by executing the call
function with the address of the contract owner. The
result is simply unwrapped in this test.
Running tests
Let's execute all tests by typing in the terminal:
cargo test
All 3 tests should pass:
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 3 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test multitest::test_counter::incrementing_should_work ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
Like in the previous examples let's run the code coverage script:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 14/22
||
63.64% coverage, 14/22 lines covered
The code coverage report (similar to the one generated by Tarpaulin) is as follows:
use crate::msg::{CounterInitMsg, CounterResponse};
use cosmwasm_std::{Response, StdResult};
use cw_storage_plus::Item;
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
pub struct CounterContract {
pub count: Item<u8>,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new("count"),
}
}
#[sv::msg(instantiate)]
fn init(&self, ctx: InstantiateCtx, msg: CounterInitMsg) -> StdResult<Response> {
match msg {
CounterInitMsg::Zero => self.count.save(ctx.deps.storage, &0)?,
CounterInitMsg::Set(value) => self.count.save(ctx.deps.storage, &value)?,
}
Ok(Response::new())
}
#[sv::msg(exec)]
fn inc(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_add(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn dec(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_sub(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn set(&self, ctx: ExecCtx, value: u8) -> StdResult<Response> {
self.count.save(ctx.deps.storage, &value)?;
Ok(Response::new())
}
#[sv::msg(query)]
fn value(&self, ctx: QueryCtx) -> StdResult<CounterResponse> {
let value = self.count.load(ctx.deps.storage)?;
Ok(CounterResponse { value })
}
}
As expected, the inc()
function was called and the counter value was incremented by 1
. Notice
that there are two additional functions dec()
and set
, that still need to be
tested for this contract. However, before we proceed, let's address the issue of counter overflow
during incrementation.
Testing increment overflow
When you recall the counter smart contract specification, you will notice that the counter value
is of type u8
, which means that the maximum value this counter can hold is equal to 255. What
happens when you increment the counter beyond 255? The following test initializes the counter with
value 250 and then increments it 10 times by calling the inc()
function.
#[test]
fn incrementing_should_stop_at_maximum() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(250))
.call(&owner)
.unwrap();
for _ in 1..=10 {
contract.inc().call(&owner).unwrap();
}
assert_eq!(255, contract.value().unwrap().value);
}
In the highlighted lines 69 to 71, the loop is executed 10 times, after which the counter value is
queried and asserted to be 255. As you will see, this test will pass, indicating that the counter
stops incrementing once it reaches value 255. This happens because the counter is incremented using
the saturating_add
function on the u8
type.
As an exercise, you can modify this test by initializing the counter with zero and incrementing it,
let say, 1000 times. This will demonstrate how quick and simple it is to test boundary values on
constrained types using MultiTest
.
Running tests
Make sure all tests pass:
cargo test
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 4 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test multitest::test_counter::incrementing_should_work ... ok
test multitest::test_counter::incrementing_should_stop_at_maximum ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
We check now the code coverage, as usual:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 14/22
||
63.64% coverage, 14/22 lines covered
The code coverage did not change after adding this test case, but a very important use case was tested: handling overflow during counter incrementation.
Testing counter decrement
Testing decrement by 1
The next function to be tested is named dec()
. Calling it should decrement the counter by
1
. The following test is similar to the one you wrote for testing counter incrementation. The
changed lines are highlighted in the following code snippet:
#[test]
fn decrementing_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(126))
.call(&owner)
.unwrap();
contract.dec().call(&owner).unwrap();
assert_eq!(125, contract.value().unwrap().value);
}
In line 85 the counter is initialized with the value 126
, then in line 89 it is decremented by 1
and in line 91 it is asserted that the final value of the counter is 125
.
Running tests
The only way to confirm that the counter is properly decremented is by running all the tests:
cargo test
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 5 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test multitest::test_counter::incrementing_should_work ... ok
test multitest::test_counter::incrementing_should_stop_at_maximum ... ok
test multitest::test_counter::decrementing_should_work ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
Run code coverage script, as usual:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 19/22
||
86.36% coverage, 19/22 lines covered
Check the code coverage report:
use crate::msg::{CounterInitMsg, CounterResponse};
use cosmwasm_std::{Response, StdResult};
use cw_storage_plus::Item;
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
pub struct CounterContract {
pub count: Item<u8>,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new("count"),
}
}
#[sv::msg(instantiate)]
fn init(&self, ctx: InstantiateCtx, msg: CounterInitMsg) -> StdResult<Response> {
match msg {
CounterInitMsg::Zero => self.count.save(ctx.deps.storage, &0)?,
CounterInitMsg::Set(value) => self.count.save(ctx.deps.storage, &value)?,
}
Ok(Response::new())
}
#[sv::msg(exec)]
fn inc(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_add(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn dec(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_sub(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn set(&self, ctx: ExecCtx, value: u8) -> StdResult<Response> {
self.count.save(ctx.deps.storage, &value)?;
Ok(Response::new())
}
#[sv::msg(query)]
fn value(&self, ctx: QueryCtx) -> StdResult<CounterResponse> {
let value = self.count.load(ctx.deps.storage)?;
Ok(CounterResponse { value })
}
}
As expected, the dec()
function is already tested.
Testing decrement underflow
Similar to the incrementation overflow test, the following test checks for underflow during counter
decrementation. The counter is initialized to 5, decremented 10 times, and the final value is
asserted to be 0. This test passes without errors because the u8
type in the smart contract is
decremented using the saturating_sub
function.
#[test]
fn decrementing_should_stop_at_minimum() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(5))
.call(&owner)
.unwrap();
for _ in 1..=10 {
contract.dec().call(&owner).unwrap();
}
assert_eq!(0, contract.value().unwrap().value);
}
Running tests
Like in previous examples, let's run all tests:
cargo test
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 6 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test multitest::test_counter::incrementing_should_work ... ok
test multitest::test_counter::incrementing_should_stop_at_maximum ... ok
test multitest::test_counter::decrementing_should_work ... ok
test multitest::test_counter::decrementing_should_stop_at_minimum ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
Run the code coverage script, again:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 19/22
||
86.36% coverage, 19/22 lines covered
The code coverage did not change after adding this test case, but a very important use case was tested: handling underflow during counter decrementation.
Testing counter value changes
The remaining red lines in the test coverage report highlight the set()
function. The test
below sets the counter directly to 125
(line 127), and the final value of the counter is expected
to be 125
.
#[test]
fn setting_value_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(5))
.call(&owner)
.unwrap();
contract.set(125).call(&owner).unwrap();
assert_eq!(125, contract.value().unwrap().value);
}
Running tests
Let’s run tests:
cargo test
All tests should pass:
Running tests/mod.rs (target/debug/deps/mod-a819a838d5b2d67d)
running 7 tests
test multitest::test_counter::instantiating_with_zero_should_work ... ok
test multitest::test_counter::instantiating_with_value_should_work ... ok
test multitest::test_counter::incrementing_should_work ... ok
test multitest::test_counter::incrementing_should_stop_at_maximum ... ok
test multitest::test_counter::decrementing_should_work ... ok
test multitest::test_counter::decrementing_should_stop_at_minimum ... ok
test multitest::test_counter::setting_value_should_work ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Code coverage
Let's generate the final code coverage report:
./coverage.sh
|| Tested/Total Lines:
|| src/contract.rs: 22/22
||
100.00% coverage, 22/22 lines covered
Nice, you have reached 💯% code coverage.
use crate::msg::{CounterInitMsg, CounterResponse};
use cosmwasm_std::{Response, StdResult};
use cw_storage_plus::Item;
use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
pub struct CounterContract {
pub count: Item<u8>,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new("count"),
}
}
#[sv::msg(instantiate)]
fn init(&self, ctx: InstantiateCtx, msg: CounterInitMsg) -> StdResult<Response> {
match msg {
CounterInitMsg::Zero => self.count.save(ctx.deps.storage, &0)?,
CounterInitMsg::Set(value) => self.count.save(ctx.deps.storage, &value)?,
}
Ok(Response::new())
}
#[sv::msg(exec)]
fn inc(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_add(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn dec(&self, ctx: ExecCtx) -> StdResult<Response> {
self.count
.update(ctx.deps.storage, |count| -> StdResult<u8> {
Ok(count.saturating_sub(1))
})?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn set(&self, ctx: ExecCtx, value: u8) -> StdResult<Response> {
self.count.save(ctx.deps.storage, &value)?;
Ok(Response::new())
}
#[sv::msg(query)]
fn value(&self, ctx: QueryCtx) -> StdResult<CounterResponse> {
let value = self.count.load(ctx.deps.storage)?;
Ok(CounterResponse { value })
}
}
All functionalities of the counter smart contract has been tested, and the code coverage report
shines green
.
Test cases put all together
Below is the final version of the test_counter.rs
file, containing all previously presented test
cases for the counter smart contract written using the Sylvia framework.
use counter::contract::sv::mt::{CodeId, CounterContractProxy};
use counter::msg::CounterInitMsg;
use sylvia::cw_multi_test::IntoAddr;
use sylvia::multitest::App;
#[test]
fn instantiating_with_zero_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Zero)
.call(&owner)
.unwrap();
assert_eq!(0, contract.value().unwrap().value);
}
#[test]
fn instantiating_with_value_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(12))
.call(&owner)
.unwrap();
assert_eq!(12, contract.value().unwrap().value);
}
#[test]
fn incrementing_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Zero)
.call(&owner)
.unwrap();
contract.inc().call(&owner).unwrap();
assert_eq!(1, contract.value().unwrap().value);
}
#[test]
fn incrementing_should_stop_at_maximum() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(250))
.call(&owner)
.unwrap();
for _ in 1..=10 {
contract.inc().call(&owner).unwrap();
}
assert_eq!(255, contract.value().unwrap().value);
}
#[test]
fn decrementing_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(126))
.call(&owner)
.unwrap();
contract.dec().call(&owner).unwrap();
assert_eq!(125, contract.value().unwrap().value);
}
#[test]
fn decrementing_should_stop_at_minimum() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(5))
.call(&owner)
.unwrap();
for _ in 1..=10 {
contract.dec().call(&owner).unwrap();
}
assert_eq!(0, contract.value().unwrap().value);
}
#[test]
fn setting_value_should_work() {
let app = App::default();
let code_id = CodeId::store_code(&app);
let owner = "owner".into_addr();
let contract = code_id
.instantiate(CounterInitMsg::Set(5))
.call(&owner)
.unwrap();
contract.set(125).call(&owner).unwrap();
assert_eq!(125, contract.value().unwrap().value);
}
Summary
In this chapter, you learned how to test a smart contract written in Sylvia, how to properly
structure effective test cases, and how to measure testing progress with code coverage reports. You
also became familiar with the cargo test
and cargo nextest run
tools. We recommend trying to
write tests for a smart contract built using pure CosmWasm libraries to experience the differences
between these two approaches.