Skip to content

Commit

Permalink
Update hello-world-testing/testing-with-gtest.md (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
SalMiah111 authored Jul 14, 2023
1 parent 868bb14 commit 960ba10
Showing 1 changed file with 46 additions and 20 deletions.
66 changes: 46 additions & 20 deletions docs/04-hello-world-testing/testing-with-gtest.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,31 @@ sidebar_position: 1
hide_table_of_contents: true
---

In this part of our course, you will learn how to test smart contracts in the Rust programming language using the Gear [`gtest`](https://docs.gear.rs/gtest/) library. The tutorial will cover creating a test file, defining a test function, initializing the environment to run the program, sending messages to the program, and checking test results.
In this part of our course, you will learn how to test smart contracts in the Rust programming language using the Gear [`gtest`](https://docs.gear.rs/gtest/) library.

Testing smart contracts is an important aspect of developing decentralized applications. We’ll use the Gear [`gtest`](https://docs.gear.rs/gtest/) library for our program’s logic testing.
Here's what we'll cover:

To get started, let's create a new directory called `tests` at the top level of our project directory, next to the `src` directory. In that directory, we’ll create a file `hello_world_test.rs` where we’ll write tests for our contract.
- Creating a test file
- Defining a test function
- Initializing the environment to run the program
- Sending messages to the program
- Checking test results

Testing smart contracts is crucial when developing decentralized applications. We'll use the Gear [`gtest`](https://docs.gear.rs/gtest/) library for our program's logic testing.

Let's start by create a new directory called `tests` at the top level of our project directory, next to the `src` directory.

We'll create the `hello_world_test.rs` file to write tests for our contract in the directory.

```bash
mkdir tests
cd tests
touch hello_world_test.rs
```

In our test file, we’ll need to import the necessary types from the [`gtest`](https://docs.gear.rs/gtest/) library, such as [`Log`](https://docs.gear.rs/gtest/struct.Log.html), [`Program`](https://docs.gear.rs/gtest/struct.Program.html) and [`System`](https://docs.gear.rs/gtest/struct.System.html). We’ll also define a test function:
In our test file, we'll import the necessary types from the [`gtest`](https://docs.gear.rs/gtest/) library, such as [`Log`](https://docs.gear.rs/gtest/struct.Log.html), [`Program`](https://docs.gear.rs/gtest/struct.Program.html) and [`System`](https://docs.gear.rs/gtest/struct.System.html).

We'll also define a test function:

```rust title="tests/hello_world_test.rs"
use gtest::{Log, Program, System};
Expand All @@ -25,13 +37,15 @@ use gtest::{Log, Program, System};
fn hello_test() {}
```

Before testing our smart contract, we need to create an environment for running programs. We can do this using the [`System`](https://docs.gear.rs/gtest/struct.System.html) structure from `gtest`. The `System` emulates the node behavior:
Before testing our smart contract, we'll create an environment for running programs using the [`System`](https://docs.gear.rs/gtest/struct.System.html) structure from `gtest`. The `System` emulates the node behavior:

```rust
let sys = System::new();
```

Next, we need to create a mockup of our program. We can do this using the [`Program`](https://docs.gear.rs/gtest/struct.Program.html) structure from `gtest`. There are two ways to create a program mockup: from a file by its path or pointing to the program itself (current program).
Next, we'll create a mockup of our program using the [`Program`](https://docs.gear.rs/gtest/struct.Program.html) structure from `gtest`. There are two ways to create a program mockup:
- From a file by its path
- By pointing to the program itself (current program)

To create a program mockup from a Wasm file:

Expand All @@ -46,14 +60,16 @@ To create a program mockup from the program itself:
let program = Program::current(&sys);
```

The uploaded program has its own id. You can specify the program id manually using the [`Program::from_file_with_id`](https://docs.gear.rs/gtest/struct.Program.html#method.from_file_with_id) constructor. If you don't specify the program id, the id of the first initialized program will be `0x010000…00` (32-byte _one_, LSB first), and the next program initialized without an id specification will have an id of `0x020000…00` (32-byte _two_, LSB first) and so on.
The uploaded program has its own id. You can specify the program id manually using the [`Program::from_file_with_id`](https://docs.gear.rs/gtest/struct.Program.html#method.from_file_with_id) constructor.

If you don't specify the program id, the id of the first initialized program will be `0x010000…00` (32-byte _one_, LSB first), and the next program initialized without an id specification will have an id of `0x020000…00` (32-byte _two_, LSB first) and so on.

In the next step, we’ll send messages to our program.

- To send a message to the program, call one of two `Program` methods: [`send`](https://docs.gear.rs/gtest/struct.Program.html#method.send) or [`send_bytes`](https://docs.gear.rs/gtest/struct.Program.html#method.send_bytes). The difference between them is similar to `gstd` functions [`msg::send`](https://docs.gear.rs/gstd/msg/fn.send.html) and [`msg::send_bytes`](https://docs.gear.rs/gstd/msg/fn.send_bytes.html).
- The first argument in these functions is a sender id, the second one is a message payload.
- The sender id can be specified as hex, byte array (`[u8; 32]`), string or `u64`. However, you cant send a message from the id already taken by the program!
- The first message to the `Program` structure is always the initialization message even if the program does not have the `init` function. In our case, it can be any message. But let’s add the `init` function to our program and monitor if that message reaches the program:
- The first argument in these functions is a sender id, and the second is a message payload.
- You can specify the sender id as a hex, byte array (`[u8; 32]`), string or `u64`. However, you can't send a message from the id already taken by the program!
- The first message sent to the `Program` structure is always the initialization message, regardless of whether the program has an `init` function or not. In our case, we can use any message. However, let's include the `init` function in our program and observe if that message reaches the program.

```rust title="src/lib.rs"
#![no_std]
Expand Down Expand Up @@ -86,7 +102,11 @@ fn hello_test() {
}
```

Note that we added `sys.init_logger()` to initialize printing logs into stdout, and we sent a message from the user with id 2 (id 2 transforms to `ActorId` equal to `0x020000…00` ).
:::note

We added `sys.init_logger()` to initialize printing logs into stdout and sent a message from the user with id 2 (id 2 transforms to `ActorId` equal to `0x020000…00` ).

:::

We can then run our test using `cargo test`:

Expand All @@ -101,11 +121,13 @@ If everything is working correctly, we should see the debug message in our conso
test hello_test ... ok
```

Sending functions in the `gtest` library will return [`RunResult`](https://docs.gear.rs/gtest/struct.RunResult.html) structure. It contains the final result of the processing message and others, which were created during the execution.
Sending functions in the `gtest` library will return [`RunResult`](https://docs.gear.rs/gtest/struct.RunResult.html) structure. It contains the final result of the processing message and other messages created during the execution.

For example, we can check the init message processing result by ensuring the log is empty and the program doesn't reply or send any messages.

For example, we can check the init message processing result. We can do this by ensuring that the log is empty and that the program does not reply or send any messages. To do this, we can use the `assert!(res.log().is_empty())` command.
To do this, we can use the `assert!(res.log().is_empty())` command.

- Contains empty log (the program doesnt reply and does not send any messages):
- Contains empty log (the program doesn't reply and does not send any messages):

```rust
assert!(res.log().is_empty());
Expand All @@ -116,16 +138,17 @@ For example, we can check the init message processing result. We can do this by
```rust
assert!(!res.main_failed());
```

Once we have confirmed that the initialization message was successful, the next messages will be processed through the `handle` function. We can test this by sending the next message using the `program.send(2, String::from("Hello"))` command.
After confirming the successful initialization message, we process the next messages through the `handle` function. To test this, we can send the next message using the `program.send(2, String::from("Hello"))` command.

```rust
let res = program.send(2, String::from("Hello"));
```

Here, we should check that the program replied with the expected hello message. To do this, we can use the [`Log`](https://docs.gear.rs/gtest/struct.Log.html) structure from the `gtest` lib and build the log we are expecting to receive. Specifically, we can use the `Log::builder().dest(2).payload(String::from("Hello"))` command to create the expected log.
Here, we'll confirm if the program replied with the expected hello message. We can accomplish this by utilizing the `Log` structure from the `gtest` library and constructing the anticipated log.

After creating the expected log, we can then check if the received log contains the expected log. We can do this by using the `assert!(res.contains(&expected_log))` command.
To create the expected log, we'll use the command `Log::builder().dest(2).payload(String::from("Hello"))`.

After creating the expected log, we can then check if the received log contains the expected log. We'll use the `assert!(res.contains(&expected_log))` command.

```rust
let expected_log = Log::builder()
Expand All @@ -134,6 +157,9 @@ let expected_log = Log::builder()
assert!(res.contains(&expected_log));
```

As you might guess, `dest` is the account to which the program sends a message and payload is the content of that message.
In this case:
- dest` represents the account where the program sends a message
- `payload` contains the content of the message


Run the test and make sure that everything is fine.
Run the test to ensure everything works correctly.

0 comments on commit 960ba10

Please sign in to comment.