Write Unit Tests

Finally, we are ready to write unit tests.

We will first write test setup functions in a separate file, then write test cases.

Test Setup

For reusable setup across tests, we will define setup functions for testing in test_utils.move:

  • setup
    • Initializes AptosCoin for testing.
    • Creates and sets up accounts for the marketplace, a seller, and a purchaser, including registering AptosCoin and minting test coins for each.

#[test_only]
module marketplace::test_utils {
use std::signer;
use std::string;
use std::vector;
use aptos_framework::account;
use aptos_framework::aptos_coin::{Self, AptosCoin};
use aptos_framework::coin;
use aptos_framework::object::{Self, Object};
use aptos_token_objects::token::Token;
use aptos_token_objects::aptos_token;
use aptos_token_objects::collection::Collection;
public inline fun setup(
aptos_framework: &signer,
marketplace: &signer,
seller: &signer,
purchaser: &signer,
): (address, address, address) {
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework);
let marketplace_addr = signer::address_of(marketplace);
account::create_account_for_test(marketplace_addr);
coin::register<AptosCoin>(marketplace);
let seller_addr = signer::address_of(seller);
account::create_account_for_test(seller_addr);
coin::register<AptosCoin>(seller);
let purchaser_addr = signer::address_of(purchaser);
account::create_account_for_test(purchaser_addr);
coin::register<AptosCoin>(purchaser);
let coins = coin::mint(10000, &mint_cap);
coin::deposit(seller_addr, coins);
let coins = coin::mint(10000, &mint_cap);
coin::deposit(purchaser_addr, coins);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
(marketplace_addr, seller_addr, purchaser_addr)
}

  • mint_tokenv2
    • Generates a test NFT collection and mints a token within it, simulating the creation of tradable digital assets.

public fun mint_tokenv2_with_collection(seller: &signer): (Object<Collection>, Object<Token>) {
let collection_name = string::utf8(b"collection_name");
let collection_object = aptos_token::create_collection_object(
seller,
string::utf8(b"collection description"),
2,
collection_name,
string::utf8(b"collection uri"),
true,
true,
true,
true,
true,
true,
true,
true,
true,
1,
100,
);
let aptos_token = aptos_token::mint_token_object(
seller,
collection_name,
string::utf8(b"description"),
string::utf8(b"token_name"),
string::utf8(b"uri"),
vector::empty(),
vector::empty(),
vector::empty(),
);
(object::convert(collection_object), object::convert(aptos_token))
}
public fun mint_tokenv2(seller: &signer): Object<Token> {
let (_collection, token) = mint_tokenv2_with_collection(seller);
token
}

Test Cases

Unit tests are placed in a dedicated test-only module, test_list_and_purchase, focusing on key functionalities:


#[test_only]
module marketplace::test_list_and_purchase {
use std::option;
use aptos_framework::aptos_coin::AptosCoin;
use aptos_framework::coin;
use aptos_framework::object::{Self, Object, ObjectCore};
use aptos_token_objects::token::Token;
use marketplace::list_and_purchase::{Self, Listing};
use marketplace::test_utils;
// Test that a fixed price listing can be created and purchased.
#[test(aptos_framework = @0x1, marketplace = @0x111, seller = @0x222, purchaser = @0x333)]
fun test_fixed_price(
aptos_framework: &signer,
marketplace: &signer,
seller: &signer,
purchaser: &signer,
) {
let (_marketplace_addr, seller_addr, purchaser_addr) =
test_utils::setup(aptos_framework, marketplace, seller, purchaser);
let (token, listing) = fixed_price_listing(seller, 500); // price: 500
assert!(list_and_purchase::listed_object(listing) == object::convert(token), 0); // The token is listed.
assert!(list_and_purchase::price<AptosCoin>(listing) == option::some(500), 0); // The price is 500.
assert!(object::owner(token) == object::object_address(&listing), 0); // The token is owned by the listing object. (escrowed)
list_and_purchase::purchase<AptosCoin>(purchaser, object::convert(listing));
assert!(object::owner(token) == purchaser_addr, 0); // The token has been transferred to the purchaser.
assert!(coin::balance<AptosCoin>(seller_addr) == 10500, 0); // The seller has been paid.
assert!(coin::balance<AptosCoin>(purchaser_addr) == 9500, 0); // The purchaser has paid.
}

  • test_fixed_price
    • Validates the creation and successful purchase of a fixed price listing, ensuring the token is correctly escrowed and transferred post-purchase, and the seller receives the payment.

#[test(aptos_framework = @0x1, marketplace = @0x111, seller = @0x222, purchaser = @0x333)]
fun test_fixed_price(
aptos_framework: &signer,
marketplace: &signer,
seller: &signer,
purchaser: &signer,
) {
let (_marketplace_addr, seller_addr, purchaser_addr) =
test_utils::setup(aptos_framework, marketplace, seller, purchaser);
let (token, listing) = fixed_price_listing(seller, 500); // price: 500
assert!(list_and_purchase::listed_object(listing) == object::convert(token), 0); // The token is listed.
assert!(list_and_purchase::price<AptosCoin>(listing) == option::some(500), 0); // The price is 500.
assert!(object::owner(token) == object::object_address(&listing), 0); // The token is owned by the listing object. (escrowed)
list_and_purchase::purchase<AptosCoin>(purchaser, object::convert(listing));
assert!(object::owner(token) == purchaser_addr, 0); // The token has been transferred to the purchaser.
assert!(coin::balance<AptosCoin>(seller_addr) == 10500, 0); // The seller has been paid.
assert!(coin::balance<AptosCoin>(purchaser_addr) == 9500, 0); // The purchaser has paid.
}

  • test_not_enough_coin_fixed_price
    • Verifies that a purchase attempt with insufficient funds fails as expected.

// Test that the purchase fails if the purchaser does not have enough coin.
#[test(aptos_framework = @0x1, marketplace = @0x111, seller = @0x222, purchaser = @0x333)]
#[expected_failure(abort_code = 0x10006, location = aptos_framework::coin)]
fun test_not_enough_coin_fixed_price(
aptos_framework: &signer,
marketplace: &signer,
seller: &signer,
purchaser: &signer,
) {
test_utils::setup(aptos_framework, marketplace, seller, purchaser);
let (_token, listing) = fixed_price_listing(seller, 100000); // price: 100000
list_and_purchase::purchase<AptosCoin>(purchaser, object::convert(listing));
}

  • test_no_listing
    • Verifies that attempts to purchase a non-existent listing object fails as expected.

// Test that the purchase fails if the listing object does not exist.
#[test(aptos_framework = @0x1, marketplace = @0x111, seller = @0x222, purchaser = @0x333)]
#[expected_failure(abort_code = 0x60001, location = marketplace::list_and_purchase)]
fun test_no_listing(
aptos_framework: &signer,
marketplace: &signer,
seller: &signer,
purchaser: &signer,
) {
let (_, seller_addr, _) = test_utils::setup(aptos_framework, marketplace, seller, purchaser);
let dummy_constructor_ref = object::create_object(seller_addr);
let dummy_object = object::object_from_constructor_ref<ObjectCore>(&dummy_constructor_ref);
list_and_purchase::purchase<AptosCoin>(purchaser, object::convert(dummy_object));
}