This site requires Javascript to be enabled.
  • Docs
  • Smart Contracts
  • Testing

Testing

In this section you can see how to write and execute unit tests for cosmwasm smart contracts.

Some best practices and tips include:

  • Test query functions: Learn to test query functions effectively by calling them with various parameters and verifying the results. This ensures that users can interact with the smart contract as intended.
  • Test error handling and edge cases: Discover how to cover error handling scenarios and edge cases in your tests. This ensures the smart contract behaves correctly in unexpected situations or when given unexpected inputs.
  • Test with custom mock dependencies: Understand how to create custom mock dependencies for your tests, simulating different scenarios and conditions that may arise during smart contract execution. This helps verify the contract's behavior under different circumstances.

In order to learn how to perform unit testing on cosmwasm smart contracts, you can have a look at the following tutorial:

At this point, your code should be compiled, even though you haven't tested whether it works or not. You can deploy the code to the blockchain each time you make a change, but this is not an efficient use of your time. It's also important to keep the contract intact and well-tested for future modifications.

#[cfg(test)]mod tests {  use super::*;  use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR};  use cosmwasm_std::{attr, coins, CosmosMsg};

You have the option to keep your tests and code in the same or separate files. See an example here.

Test initialization

For each test, it is important to mock specific variables, such as block time and state. Writing a function for easy setup can make this process more manageable.

#[test]fn proper_initialization() {  let mut deps = mock_dependencies(&[]);  let msg = InitMsg {    counter_offer: coins(40, "ETH"),    expires: 100_000,  };  let info = mock_info("creator", &coins(1, "BTC"));  // we can just call .unwrap() to assert this was a success  let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();  assert_eq!(0, res.messages.len());  // it worked, let's query the state  let res = query_config(deps.as_ref()).unwrap();  assert_eq!(100_000, res.expires);  assert_eq!("creator", res.owner.as_str());  assert_eq!("creator", res.creator.as_str());  assert_eq!(coins(1, "BTC"), res.collateral);  assert_eq!(coins(40, "ETH"), res.counter_offer);}

Great, you now have a test environment initializer. This is a quite basic one; you can pass variables to the function and make various modifications. Check out cosmwasm-plus for more options.

Mock dependencies, environment, and message info

There are three mocking tools that we should improve on:

/// All external requirements that can be injected for unit tests./// It sets the given balance for the contract itself, nothing elsepub fn mock_dependencies(  contract_balance: &[Coin],) -> OwnedDeps<MockStorage, MockApi, MockQuerier> {  let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR);  OwnedDeps {    storage: MockStorage::default(),    api: MockApi::default(),    querier: MockQuerier::new(&[(&contract_addr, contract_balance)]),  }}

mock_dependencies is used for mocking storage, api, and querier.

/// Returns a default enviroment with height, time, chain_id, and contract address./// You can submit as is to most contracts, or modify height/time if you want to/// test for expiration.////// This is intended for use in test code only.pub fn mock_env() -> Env {  Env {    block: BlockInfo {      height: 12_345,      time: 1_571_797_419,      time_nanos: 879305533,      chain_id: "archway-testnet-14002".to_string(),    },    contract: ContractInfo {      address: HumanAddr::from(MOCK_CONTRACT_ADDR),    },  }}

mock_env is used for mocking blocks, and contract environments.

/// Just set sender and sent funds for the message./// This is intended for use in test code only.pub fn mock_info<U: Into<HumanAddr>>(sender: U, sent: &[Coin]) -> MessageInfo {  MessageInfo {    sender: sender.into(),    sent_funds: sent.to_vec(),  }}

mock_info is used for mocking transaction environments.

Test handler

Test transfer handler

#[test]fn transfer() {  let mut deps = mock_dependencies(&[]);  let msg = InitMsg {    counter_offer: coins(40, "ETH"),    expires: 100_000,  };  let info = mock_info("creator", &coins(1, "BTC"));  // we can just call .unwrap() to assert this was a success  let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();  assert_eq!(0, res.messages.len());  // random cannot transfer  let info = mock_info("anyone", &[]);  let err = handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("anyone"))    .unwrap_err();  match err {    ContractError::Unauthorized {} => {}    e => panic!("unexpected error: {}", e),  }  // owner can transfer  let info = mock_info("creator", &[]);  let res =    handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("someone")).unwrap();  assert_eq!(res.attributes.len(), 2);  assert_eq!(res.attributes[0], attr("action", "transfer"));  // check updated properly  let res = query_config(deps.as_ref()).unwrap();  assert_eq!("someone", res.owner.as_str());  assert_eq!("creator", res.creator.as_str());}

Test execute

#[test]fn execute() {  let mut deps = mock_dependencies(&[]);  let amount = coins(40, "ETH");  let collateral = coins(1, "BTC");  let expires = 100_000;  let msg = InitMsg {    counter_offer: amount.clone(),    expires: expires,  };  let info = mock_info("creator", &collateral);  // we can just call .unwrap() to assert this was a success  let _ = init(deps.as_mut(), mock_env(), info, msg).unwrap();  // set new owner  let info = mock_info("creator", &[]);  let _ = handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("owner")).unwrap();  // random cannot execute  let info = mock_info("creator", &amount);  let err = handle_execute(deps.as_mut(), mock_env(), info).unwrap_err();  match err {    ContractError::Unauthorized {} => {}    e => panic!("unexpected error: {}", e),  }  // expired cannot execute  let info = mock_info("owner", &amount);  let mut env = mock_env();  env.block.height = 200_000;  let err = handle_execute(deps.as_mut(), env, info).unwrap_err();  match err {    ContractError::OptionExpired { expired } => assert_eq!(expired, expires),    e => panic!("unexpected error: {}", e),  }  // bad counter_offer cannot execute  let msg_offer = coins(39, "ETH");  let info = mock_info("owner", &msg_offer);  let err = handle_execute(deps.as_mut(), mock_env(), info).unwrap_err();  match err {    ContractError::CounterOfferMismatch {      offer,      counter_offer,    } => {      assert_eq!(msg_offer, offer);      assert_eq!(amount, counter_offer);    }    e => panic!("unexpected error: {}", e),  }  // proper execution  let info = mock_info("owner", &amount);  let res = handle_execute(deps.as_mut(), mock_env(), info).unwrap();  assert_eq!(res.messages.len(), 2);  assert_eq!(    res.messages[0],    CosmosMsg::Bank(BankMsg::Send {      from_address: MOCK_CONTRACT_ADDR.into(),      to_address: "creator".into(),      amount,    })  );  assert_eq!(    res.messages[1],    CosmosMsg::Bank(BankMsg::Send {      from_address: MOCK_CONTRACT_ADDR.into(),      to_address: "owner".into(),      amount: collateral,    })  );  // check deleted  let _ = query_config(deps.as_ref()).unwrap_err();}

Now run the tests:

cargo test

If everything is green, the code will work on the chain.

Drop Camp is here!

Join the queue and be one of the first to get in!.

Go Camping ↗