A common use of Sway is for writing contracts or scripts that exist as part of a
wider Rust application. In order to test the interaction between our Sway code
and our Rust code we can add integration testing.
To add Rust integration testing to a Forc project we can use the sway-test-rs
cargo generate
template Icon Link.
This template makes it easier for Sway developers to add the boilerplate required when
setting up their Rust integration testing.
--force forces your --name input to retain your desired casing for the {{project-name}}
placeholder in the template. Otherwise, cargo-generate automatically converts it to kebab-case.
With --force, this means that both my_fuel_project and my-fuel-project are valid project names,
depending on your needs.
If all goes well, the output should look as follows:
⚠️ Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway🤷 Project Name : my-fuel-project🔧 Destination: /home/user/path/to/my-fuel-project ...🔧 Generating template ...[1/3] Done: Cargo.toml[2/3] Done: tests/harness.rs[3/3] Done: tests🔧 Moving generated files into: `/home/user/path/to/my-fuel-project`...✨ Done! New project created /home/user/path/to/my-fuel-project
Before running the tests, we need to build our contract so that the necessary
ABI, storage and bytecode artifacts are available. We can do so with forc build:
$ forc build Creating a new `Forc.lock` file. (Cause: lock file did not exist) Adding core Adding std git+https://github.com/fuellabs/sway?tag=v0.24.5#e695606d8884a18664f6231681333a784e623bc9 Created new lock file at /home/user/path/to/my-fuel-project/Forc.lock Compiled library "core". Compiled library "std". Compiled contract "my-fuel-project". Bytecode size is 60 bytes.
At this point, our project should look like the following:
We now have an out directory with our required JSON files!
Icon InfoCircle
Note: This step may no longer be required in the future as we plan to
enable the integration testing to automatically build the artifacts as
necessary so that files like the ABI JSON are always up to date.
Now we're ready to build and run the default integration test.
$ cargo test Updating crates.io index Compiling version_check v0.9.4 Compiling proc-macro2 v1.0.46 Compiling quote v1.0.21 ... Compiling fuels v0.24.0 Compiling my-fuel-project v0.1.0 (/home/user/path/to/my-fuel-project) Finished test [unoptimized + debuginfo] target(s) in 1m 03s Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)running 1 testtest can_get_contract_id ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s
Icon InfoCircle
Note: The first time we run cargo test, cargo will spend some time
fetching and building the dependencies for Fuel's Rust SDK. This might take a
while, but only the first time!
If all went well, we should see some output that looks like the above!
To test our initialize_counter and increment_counter contract methods from
the Rust test harness, we could update our tests/harness.rs file with the
following:
use fuels::{prelude::*, types::ContractId};// Load ABI from JSONabigen!(TestContract, "out/debug/my-fuel-project-abi.json");asyncfnget_contract_instance() -> (TestContract, ContractId) { // Launch a local network and deploy the contractletmut wallets =launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(1), /* Single wallet */Some(1), /* Single coin (UTXO) */Some(1_000_000_000), /* Amount per coin */ ),None, ).await;let wallet = wallets.pop().unwrap();let id =Contract::load_from("./out/debug/my-fuel-project.bin",LoadConfiguration::default().set_storage_configuration(StorageConfiguration::load_from("./out/debug/my-fuel-project-storage_slots.json", ).unwrap(), ), ).unwrap().deploy(&wallet, TxParameters::default()).await.unwrap();let instance =TestContract::new(id.to_string(), wallet); (instance, id.into())}#[tokio::test]asyncfninitialize_and_increment() {let (contract_instance, _id) =get_contract_instance().await; // Now you have an instance of your contract you can use to test each functionlet result = contract_instance.methods().initialize_counter(42).call().await.unwrap();assert_eq!(42, result.value); // Call `increment_counter()` method in our deployed contract.let result = contract_instance.methods().increment_counter(10).call().await.unwrap();assert_eq!(52, result.value);}
Let's build our project once more and run the test:
forc build
$ cargo test Compiling my-fuel-project v0.1.0 (/home/mindtree/programming/sway/my-fuel-project) Finished test [unoptimized + debuginfo] target(s) in 11.61s Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)running 1 testtest initialize_and_increment ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.25s
When cargo runs our test, our test uses the SDK to spin up a local in-memory
Fuel network, deploy our contract to it, and call the contract methods via the
ABI.
You can add as many functions decorated with #[tokio::test] as you like, and
cargo test will automatically test each of them!