Skip to content

Commit 46433b7

Browse files
committed
Added testing query chapter.
1 parent 715988d commit 46433b7

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
sidebar_position: 5
3+
---
4+
5+
# Testing a query
6+
7+
Last time we created a new query. Now it is time to test it out.
8+
9+
We will start with the basics: the unit test. This approach is simple and doesn't require knowledge besides Rust.
10+
Go to the `src/contract.rs` and add a test in its module:
11+
12+
```rust title="src/contract.rs"
13+
// ...
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use super::*;
18+
19+
#[test]
20+
fn greet_query() {
21+
let resp = query::greet().unwrap();
22+
assert_eq!(
23+
resp,
24+
GreetResp {
25+
message: "Hello World".to_owned()
26+
}
27+
);
28+
}
29+
}
30+
```
31+
32+
If you ever wrote a unit test in Rust, nothing should surprise you here. Just a simple test-only
33+
module which contains local function unit tests. The problem is that this test doesn't build yet.
34+
We need to tweak our message types a bit.
35+
36+
```rust title="src/msg.rs" {3,8}
37+
use serde::{Deserialize, Serialize};
38+
39+
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
40+
pub struct GreetResp {
41+
pub message: String,
42+
}
43+
44+
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
45+
pub enum QueryMsg {
46+
Greet {},
47+
}
48+
```
49+
50+
We have added three new derives to both message types. [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
51+
is required to allow comparing types for equality, so we can check if they are equal.
52+
[`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) is a trait generating debug-printing utilities.
53+
It is used by [`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html) to display information about mismatch
54+
if an assertion fails. Note, that because we are not testing the `QueryMsg` in any way, the additional trait derives are optional.
55+
Still, it is a good practice to make all messages both `PartialEq` and `Debug` for testability and consistency.
56+
The last one, [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) is not needed yet, but it is also
57+
a good practice to allow messages to be cloned around. We will also require that later.
58+
59+
Now we are ready to run our test:
60+
61+
```shell title="terminalL"
62+
cargo test
63+
```
64+
65+
```shell title="terminalL"
66+
...
67+
running 1 test
68+
test contract::tests::greet_query ... ok
69+
70+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
71+
```
72+
73+
Yay! Test passed!
74+
75+
## Contract as a black box
76+
77+
Now let's go a step further. The Rust testing utility is a friendly tool for building even higher-level tests.
78+
We are currently testing smart contract internals, but think about what your smart contract looks like
79+
from the outside world. It is a single entity that is triggered by some input messages.
80+
We can create tests that treat the whole contract as a black box by testing it via our `query` function.
81+
82+
Let's update our test:
83+
84+
```rust title="src/contract.rs"
85+
// ...
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use cosmwasm_std::from_json;
90+
use cosmwasm_std::testing::{mock_dependencies, mock_env};
91+
92+
use super::*;
93+
94+
#[test]
95+
fn greet_query() {
96+
let resp = query(
97+
mock_dependencies().as_ref(),
98+
mock_env(),
99+
QueryMsg::Greet {}
100+
).unwrap();
101+
let resp: GreetResp = from_json(&resp).unwrap();
102+
103+
assert_eq!(
104+
resp,
105+
GreetResp {
106+
message: "Hello World".to_owned()
107+
}
108+
);
109+
}
110+
}
111+
```
112+
113+
We needed to produce two entities for the `query` functions: the `deps` and `env` instances.
114+
Fortunately, `cosmwasm-std` provides utilities for testing those:
115+
[`mock_dependencies()`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/fn.mock_dependencies.html) and
116+
[`mock_env()`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/fn.mock_env.html) functions.
117+
118+
You may notice the dependencies mock is of type
119+
[`OwnedDeps`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.OwnedDeps.html) instead of
120+
`Deps`, which we need here, this is why the
121+
[`as_ref`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.OwnedDeps.html#method.as_ref)
122+
function is called on it. If we needed a `DepsMut` object, we would use
123+
[`as_mut`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.OwnedDeps.html#method.as_mut)
124+
instead.
125+
126+
We can rerun the test, and it should still pass. But when we think about that test reflecting the
127+
actual use case, it is inaccurate. The contract is queried, but it was never instantiated!
128+
In software engineering, it is equivalent to calling a getter without constructing an object,
129+
taking it out of nowhere. It is a lousy testing approach. We can do better:
130+
131+
```rust title="src/contract.rs"
132+
// ...
133+
134+
#[cfg(test)]
135+
mod tests {
136+
use cosmwasm_std::from_json;
137+
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
138+
139+
use super::*;
140+
141+
#[test]
142+
fn greet_query() {
143+
let mut deps = mock_dependencies();
144+
let env = mock_env();
145+
let sender = "sender".into_addr();
146+
147+
instantiate(
148+
deps.as_mut(),
149+
env.clone(),
150+
mock_info("sender", &[]),
151+
Empty {},
152+
)
153+
.unwrap();
154+
155+
let resp = query(deps.as_ref(), env, QueryMsg::Greet {}).unwrap();
156+
let resp: GreetResp = from_json(&resp).unwrap();
157+
assert_eq!(
158+
resp,
159+
GreetResp {
160+
message: "Hello World".to_owned()
161+
}
162+
);
163+
}
164+
}
165+
```
166+
167+
A couple of new things are here. First, we have extracted the `deps` and `env` to separate variables
168+
and passed them to calls. The idea is that those variables represent some blockchain persistent state,
169+
and we don't want to create them for every call. We want any changes to the contract state occurring
170+
in `instantiate` to be visible in the `query`. Also, we want to control how the environment differs
171+
on the query and instantiation.
172+
173+
The `info` argument is another story. The message info is unique for each message sent.
174+
To create the `info` mock, we must pass two arguments to the
175+
[`mock_info`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/fn.mock_info.html) function.
176+
177+
First is the address performing the call. It may look strange to pass `sender` as an address instead
178+
of some mysterious `wasm` followed by hash. For testing purposes, such addresses are typically
179+
better, as they are way more readable in case of failing tests.
180+
181+
The second argument is funds sent with the message. For now, we leave it as an empty slice,
182+
as we don't want to talk about token transfers yet, we will cover it later.
183+
184+
So now it is more a real-case scenario. We see here just one small problem.
185+
We could say that the contract is a single black box. But here, nothing connects the `instantiate` call
186+
to the corresponding `query`. It seems that we assume there is some global contract.
187+
But it seems that if we would like to have two contracts instantiated differently in a single test case,
188+
it would become a mess. If only there would be some tool to abstract this for us, wouldn't it be nice?...

0 commit comments

Comments
 (0)