Unit testing is a crucial part of software development, helping to ensure the correct functionality of your code and enabling you to catch any bugs or issues early in the development process.
In this section, we will explore the process of executing unit tests within a CosmWasm smart contract.
To write unit tests in Rust for CosmWasm smart contracts, you need to use the #test attribute to mark test functions. These functions should be placed in a tests module inside your smart contract source code, typically using a #cfg(test) attribute to only compile them during testing. You can use macros like assert!, assert_eq!, and assert_ne! to validate your code's behavior against expected outcomes.
Run the following command to perform the available unit tests:
RUST_BACKTRACE=1 cargo unit-test
After some compilation steps, you should observe:
running 15 tests test coin_helpers::test::assert_sent_sufficient_coin_works ... ok test tests::test_module::fails_on_register_insufficient_fees ... ok test tests::test_module::fails_on_register_wrong_fee_denom ... ok test tests::test_module::proper_init_no_fees ... ok test tests::test_module::fails_on_register_already_taken_name ... ok test tests::test_module::fails_on_transfer_insufficient_fees ... ok test tests::test_module::proper_init_with_fees ... ok test tests::test_module::register_available_name_and_query_works ... ok test tests::test_module::fails_on_transfer_non_existent ... ok test tests::test_module::fails_on_transfer_from_nonowner ... ok test tests::test_module::register_available_name_fails_with_invalid_name ... ok test tests::test_module::register_available_name_and_query_works_with_fees ... ok test tests::test_module::returns_empty_on_query_unregistered_name ... ok test tests::test_module::transfer_works ... ok test tests::test_module::transfer_works_with_fees ... ok test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Using RUST_BACKTRACE=1 will provide you with full stack traces for any errors, which is extremely useful. This only works for unit tests (which test native Rust code, not the compiled wasm). Additionally, if you're curious about the origin of cargo wasm and cargo unit-test, they are simply aliases defined in the .cargo/config file located in the project directory. Examining the file contents will give you a better understanding of the cargo flags.
Mocking and dependency injection are techniques that can help you isolate specific parts of your smart contract for testing. With mocking, you create fictitious objects or functions that replace the actual dependencies of your smart contract, allowing you to control their behavior during testing. Dependency injection involves passing these mocked dependencies as arguments to your smart contract functions, which allows you to simulate different conditions and scenarios without affecting the actual contract state or dependencies.
Some best practices for writing unit tests include:
- Organize tests by placing them in separate modules or files, grouped by the functionality they test.
- Follow naming conventions, such as using a descriptive name that indicates what the test is checking, like test_transfer_funds_success or test_invalid_name_rejection.
- Write clear, concise tests that focus on individual components of the smart contract, avoiding overly complex or unrelated tests.
- Test edge cases and potential failure scenarios to ensure your smart contract can handle different situations. Keep tests independent of each other, so that the failure of one test does not affect the others.