You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(docs): added docs and examples for iota::test_scenario and utils … (#7815)
# Description of change
This PR brings up the addition of the example for test_scenario and
utils modules
## Links to any relevant issues
fixes#7780.
## How the change has been tested
- [x] Basic tests (linting, compilation, formatting, unit/integration
tests)
- [ ] Patch-specific tests (correctness, functionality coverage)
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] I have checked that new and existing unit tests pass locally with
my changes
---------
Co-authored-by: Dr-Electron <[email protected]>
@@ -127,10 +112,7 @@ Move has many features to ensure your code is safe. In this case, the `Sword` st
127
112
128
113
Instead, you can fix the compilation error by adequately disposing of the `sword`. Add the following after the function's `!assert` call to transfer the `sword` to a freshly created dummy address:
Run the test command again. Now the output shows a single successful test has run:
@@ -186,248 +168,152 @@ The `test_scenario` module allows you to emulate a series of IOTA transactions.
186
168
187
169
#### Instantiate a Scenario
188
170
189
-
You can use the `test_scenario::begin` function to create an instance of `Scenario`. The `test_scenario::begin` function takes an address as an argument, which will be used as the user executing the transaction.
171
+
You can use the `test_scenario::begin` function to create an instance of `Scenario`.
172
+
173
+
The `test_scenario::begin` function takes an address as an argument, which will be used as the user executing the transaction.
190
174
191
175
#### Add More Transactions
192
176
193
-
The `Scenario` instance will emulate the IOTA object storage with an object pool for every address. Once you have [instantiated the `Scenario`](#instantiate-a-scenario) with the first transaction, you can use the `test_scenario::next_tx` function to execute subsequent transactions. You will need to pass the current `Scenario` instance as the first argument, as well as an address for the test user sending the transaction.
177
+
The `Scenario` instance will emulate the IOTA object storage with an object pool for every address. Once you have [`instantiated the Scenario`](#instantiate-a-scenario) with the first transaction, you can use the `test_scenario::next_tx` function to execute subsequent transactions. You will need to pass the current `Scenario` instance as the first argument, as well as an address for the test user sending the transaction.
194
178
195
179
You should update the `first_package.move` file to include entry functions callable from IOTA that implement `sword` creation and transfer. You can add these after the accessor functions.
196
180
197
181
198
-
```move
199
-
public fun create_sword(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) {
200
-
// Create a sword.
201
-
let sword = Sword {
202
-
id: object::new(ctx),
203
-
magic: magic,
204
-
strength: strength,
205
-
};
206
-
// Transfer the sword.
207
-
transfer::transfer(sword, recipient);
208
-
}
209
-
210
-
public fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) {
With this code, you have enabled creating and transferring a `sword`. Since these functions use IOTA's `TxContext` and `Transfer`, you should use the `test_scenario`'s multi-transaction capability to test these properly. You should add the following test to the `first_package.move` file:
217
186
218
-
```move
219
-
#[test]
220
-
fun test_sword_transactions() {
221
-
use iota::test_scenario;
222
-
223
-
// Create test addresses representing users.
224
-
let admin = @0xBABE;
225
-
let initial_owner = @0xCAFE;
226
-
let final_owner = @0xFACE;
227
-
228
-
// First transaction to emulate module initialization.
229
-
let mut scenario_val = test_scenario::begin(admin);
230
-
let scenario = &mut scenario_val;
231
-
{
232
-
init(test_scenario::ctx(scenario));
233
-
};
234
-
// Second transaction executed by admin to create a sword.
235
-
test_scenario::next_tx(scenario, admin);
236
-
{
237
-
// Create the sword and transfer it to the initial owner.
2. Create a scenario by calling `test_scenario:begin()`, and passing the `admin` address as a parameter. You can then use the `test_scenario`'s `ctx` to emulate the module's initialization:
197
+
Let's break it down by steps so you understand how the `test_scenario` helpers work in a realistic multi-transaction flow:
275
198
276
-
```move
277
-
let scenario_val = test_scenario::begin(admin);
278
-
let scenario = &mut scenario_val;
279
-
{
280
-
init(test_scenario::ctx(scenario));
281
-
};
282
-
```
283
-
284
-
3. The `admin` creates a `sword` and sends it to the `initial_owner`. Note that the first call is to `test_scenario::next_tx`, passing the `Scenario` that was instantiated in step `2`:
285
-
286
-
```move
287
-
test_scenario::next_tx(scenario, admin);
288
-
{
289
-
// create the sword and transfer it to the initial owner
4. After advancing the `test_scenario` with `test_scenario::next_tx`, you can emulate the `initial_owner` sending the `sword` to the `final_owner`. However, in standard Move tests, there is no object storage, so it is impossible to retrieve the `sword` created in step `3`. You should use the `test_scenario::take_from_sender` function to extract the `sword`. In this case, the test transfers the object it retrieves from storage to another address:
295
-
296
-
```move
297
-
test_scenario::next_tx(scenario, initial_owner);
298
-
{
299
-
// extract the sword owned by the initial owner
300
-
let sword = test_scenario::take_from_sender<Sword>(scenario);
Transaction effects, such as object creation and transfer, become visible only after a given transaction completes. For example, if the second transaction in the running example created a `sword` and transferred it to the administrator's address, it would only become available for retrieval from the administrator's address (via `test_scenario`, `take_from_sender`, or `take_from_address` functions) in the third transaction.
309
-
310
-
:::
311
-
312
-
5. Finally, the `final_owner` retrieves the `sword` object from storage and verifies it has the expected properties.
313
-
314
-
```move
315
-
test_scenario::next_tx(scenario, final_owner);
316
-
{
317
-
// extract the sword owned by the final owner
318
-
let sword = test_scenario::take_from_sender<Sword>(scenario);
// or uncomment the line below to destroy the sword instead
324
-
// test_utils::destroy(sword)
325
-
};
326
-
test_scenario::end(scenario_val);
327
-
```
199
+
### 1. Create User Addresses
200
+
201
+
First, define three test addresses to represent different users in your scenario: an admin: ADMIN, an initial sword owner: ALICE, and a final sword owner: BOB.
Since [the `sword` cannot simply disappear](#debugging-tests), the function transfers the `sword` object to the fake address using the `test_scenario::return_to_sender` function. If returning the object to a dummy address is not your desired behavior, you can also call the [`test_utils:destroy`](https://github.com/iotaledger/iota/blob/develop/crates/iota-framework/packages/iota-framework/sources/test/test_utils.move) function.
214
+
### 3. Admin Creates a Sword and Transfers It
330
215
331
-
:::tip Know your toolbox
216
+
After the module is initialized, the `admin` runs a transaction to create a new sword.
217
+
Use `test_scenario::next_tx` to advance to the next transaction in the scenario and execute it as the `admin`.
332
218
333
-
This guide only covers the basics. You should check out the available functions for both [`test_scenario`](https://github.com/iotaledger/iota/blob/develop/crates/iota-framework/packages/iota-framework/sources/test/test_scenario.move) and [`test_utils`](https://github.com/iotaledger/iota/blob/develop/crates/iota-framework/packages/iota-framework/sources/test/test_utils.move) modules to see everything you can do.
219
+
In this example, the `admin` uses the `new_sword` function to create a sword using a `Forge` object and then transfers it to the `initial_owner`.
In `test_scenario`, transaction effects (such as creating or transferring an object) are only available to retrieve in the **next** transaction.
235
+
236
+
For example, if the second transaction in a scenario created a `sword` and transferred it to the administrator's address, it would only become available for retrieval from the administrator's address (via `test_scenario`, `take_from_sender`, or `take_from_address`) in the third transaction.
237
+
238
+
If needed, you can also use `take_from_address` to fetch an object from a specific address instead of the current sender:
239
+
240
+
```move
241
+
let sword = test_scenario::take_from_address<Sword>(scenario, initial_owner);
242
+
```
335
243
:::
336
244
337
-
If you run the test command again, you should see two successful tests for the module:
IOTA modules can include an [initializer function](create-a-module.mdx#module-initializer) that can only be run when the module is published for the first time. The `iota move` command does not support test publishing, but you can test module initializers using the testing framework to test the `Forge` object is properly created.
255
+
The `iota::test_utils` module provides useful helpers for assertions and cleanup.
256
+
This makes your test checks easier to read and maintain.
352
257
353
-
The first thing you should do is add a `new_sword` function that takes a `Forge` as a parameter, and updates its `swords_created` attribute.
258
+
#### Key Functions:
354
259
355
-
:::danger Redundant Code
260
+
`assert_eq<T>(t1: T, t2: T)`
261
+
Fails the test if t1 and t2 are not equal.
356
262
357
-
If this were an actual module, you should replace the `create_sword` function with `new_sword`, as they do the same thing. However, to keep the existing tests from failing this guide will keep both functions.
You can now add the following code to the end of the module to test the module initializer using the `test_scenario` module by calling the module's `init` function and then asserting the number of swords is zero:
283
+
### Run All Tests
377
284
378
-
```move
379
-
#[test]
380
-
fun test_sword_transactions() {
381
-
use iota::test_scenario;
382
-
383
-
// Create test addresses representing users.
384
-
let admin = @0xBABE;
385
-
let initial_owner = @0xCAFE;
386
-
let final_owner = @0xFACE;
387
-
388
-
// First transaction to emulate module initialization.
389
-
let mut scenario_val = test_scenario::begin(admin);
390
-
let scenario = &mut scenario_val;
391
-
{
392
-
init(test_scenario::ctx(scenario));
393
-
};
394
-
// Second transaction executed by admin to create a sword.
395
-
test_scenario::next_tx(scenario, admin);
396
-
{
397
-
let mut forge = test_scenario::take_from_sender<Forge>(scenario);
398
-
399
-
// Create the sword and transfer it to the initial owner.
400
-
let sword = new_sword(&mut forge, 42, 7, test_scenario::ctx(scenario));
401
-
transfer::public_transfer(sword, initial_owner);
402
-
403
-
// Return the forge to the sender.
404
-
test_scenario::return_to_sender(scenario, forge);
405
-
};
406
-
// Third transaction executed by the initial sword owner.
407
-
test_scenario::next_tx(scenario, initial_owner);
408
-
{
409
-
// Extract the sword owned by the initial owner.
410
-
let sword = test_scenario::take_from_sender<Sword>(scenario);
// or uncomment the line below to destroy the sword instead.
424
-
// test_utils::destroy(sword)
425
-
};
426
-
test_scenario::end(scenario_val);
427
-
}
285
+
1.
286
+
```shell
287
+
iota move build
428
288
```
429
289
430
-
You can refer to the source code for the package (with all the tests and functions properly adjusted) in the [first_package](https://github.com/iotaledger/iota/blob/develop/examples/move/first_package/sources/first_package.move) module in the `iota/examples` directory.
0 commit comments