Make unit test in a smart contract developed in ink ! 🦑

Make unit test in a smart contract developed in ink ! 🦑

I highly recommend you to read my two previous tutorials Analyze, compile and deploy a smart contract and Create a smart contract in ink!

Nowadays in version 4.x of ink! there is no way to divide the smart contract code and the tests. So at the end of the contract, we will add all our unit tests:

    #[cfg(test)]
    mod tests {
        /// Imports all the definitions from the outer scope so we can use them here.
        use super::*;

        // !!!!!
        // Here we are going to add all our tests
        // !!!!!  
    }

Let's start writing the tests:

fn create_contract() -> OpenColors {
    let sender = default_accounts().alice;
    set_sender(sender);

    // initial_colors -> function with pre.defined colors
    OpenColors::new(initial_colors()) 
}

#[ink::test]
fn basic_contract_creation() {
    let mut open_colors = create_contract();
    assert_eq!(
        open_colors.get_last_color(),
        Some(Color {
            r: 255,
            g: 255,
            b: 255
        })
    );
    assert_eq!(open_colors.get_colors_list(), initial_colors());
    assert_eq!(open_colors.colors_added_per_user.get(alice()).unwrap(), 2);
    assert_eq!(open_colors.owner, alice());
}

The function to be tested, we must have the attribute #[ink::test] before the declaration.

The first check that the assert_eq is doing is to check if the contract call to get_last_color() is the same to a specific color.

#[ink::test]
fn add_a_color() {
    let mut open_colors = create_contract();

    set_sender(bob());
    // call the contract to add an specific color
    open_colors.add_color(Color { r: 255, g: 0, b: 0 });

    let final_colors = vec![
        Color { r: 0, g: 0, b: 0 },
        Color {
            r: 255,
            g: 255,
            b: 255,
        },
        Color { r: 255, g: 0, b: 0 },
    ];
    assert_eq!(open_colors.get_colors_list(), final_colors);
    assert_eq!(open_colors.colors_added_per_user.get(bob()).unwrap(), 1);
    assert_eq!(open_colors.last_color, Some(Color { r: 255, g: 0, b: 0 }));
}

The test function add_a_color, create a default contract, set who is going to be the sender of the next executions and call the contract to add a specific color. Later on, the function checks the total list of colors with an array, the number of colors added by Bob and the last color.

fn alice() -> AccountId {
   default_accounts().alice
}

fn bob() -> AccountId {
    default_accounts().bob
}

fn set_sender(sender: AccountId) {
    ink::env::test::set_caller::<ink::env::DefaultEnvironment>(sender);
}

fn default_accounts() -> ink::env::test::DefaultAccounts<ink::env::DefaultEnvironment> {
    ink::env::test::default_accounts::<ink::env::DefaultEnvironment>()
}

fn advance_n_blocks(n: u32) {
    for _ in 0..n {
        ink::env::test::advance_block::<ink::env::DefaultEnvironment>();
    }
}

fn get_current_block() -> u32 {
    ink::env::block_number::<ink::env::DefaultEnvironment>()
}

fn get_balance(account_id: AccountId) -> Balance {
    ink::env::test::get_account_balance::<ink::env::DefaultEnvironment>(account_id)
                .expect("Cannot get account balance")
}

These are some functions that could help you to code faster some unit tests.

Full smart contract and test code: github.com/rtomas/openColors/blob/main/lib.rs

Finally, execute in the terminal cargo test to run and validate all the tests.

Reference: