lesson-3_作成した機能をテストしよう
✅ 投票機能をテストしてみよう
前回までのセクションで投票機能を実装してコントラクトとしては完成したので実際に機能しているかターミナル上で確認してみましょう!
テストの方法は2つあり
- テスト用の関数を走らせて、思った通りの挙動をするか一気にテストする
- 実際にdeploy してターミナル上から関数を動かして確認する
です。
まずは手軽にできる1つ目の方法でテストして、それが成功したら2つ目のテストをしましょう。
1つ目のテストは他の人がすぐにコードのテストができるという意味でも重要なのでテストを入れることを癖づけていきましょう!
lib.rs
に移動して以下のようにコードを追加してください。
注意点として、追加するテストはimpl Contract
の外に書いてください!
[lib.rs]
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::{LazyOption, LookupMap, UnorderedMap, UnorderedSet};
use near_sdk::json_types::U128;
use near_sdk::serde::{Deserialize, Serialize};
use near_sdk::{env, near_bindgen, AccountId, Balance, CryptoHash, PanicOnDefault, Promise};
mod enumeration;
mod internal;
mod metadata;
mod mint;
mod nft_core;
mod vote;
pub use crate::enumeration::*;
use crate::internal::*;
pub use crate::metadata::*;
pub use crate::mint::*;
pub use crate::nft_core::*;
pub use vote::*;
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Contract {
// contract state value
pub owner_id: AccountId,
pub tokens_per_owner: LookupMap<AccountId, UnorderedSet<TokenId>>,
pub tokens_per_kind: LookupMap<TokenKind, UnorderedSet<TokenId>>,
pub tokens_by_id: LookupMap<TokenId, TokenOwner>,
pub token_metadata_by_id: UnorderedMap<TokenId, TokenMetadata>,
pub metadata: LazyOption<NFTContractMetadata>,
pub token_id_counter: u128,
pub likes_per_candidate: LookupMap<TokenId, Likes>,
pub added_voter_list: LookupMap<ReceiverId, TokenId>,
pub voted_voter_list: LookupMap<ReceiverId, u128>,
pub is_election_closed: bool,
}
#[derive(BorshSerialize)]
pub enum StorageKey {
TokensPerOwner,
TokensPerKind,
TokensPerOwnerInner { account_id_hash: CryptoHash },
TokensPerKindInner { token_kind: TokenKind },
TokensById,
TokenMetadataById,
TokensPerTypeInner { token_type_hash: CryptoHash },
NFTContractMetadata,
LikesPerCandidate,
AddedVoterList,
VotedVoterList,
}
#[near_bindgen]
impl Contract {
// function for initialization(new_default_meta)
#[init]
pub fn new(owner_id: AccountId, metadata: NFTContractMetadata) -> Self {
let this = Self {
owner_id,
tokens_per_owner: LookupMap::new(StorageKey::TokensPerOwner.try_to_vec().unwrap()),
tokens_per_kind: LookupMap::new(StorageKey::TokensPerKind.try_to_vec().unwrap()),
tokens_by_id: LookupMap::new(StorageKey::TokensById.try_to_vec().unwrap()),
token_metadata_by_id: UnorderedMap::new(
StorageKey::TokenMetadataById.try_to_vec().unwrap(),
),
metadata: LazyOption::new(
StorageKey::NFTContractMetadata.try_to_vec().unwrap(),
Some(&metadata),
),
token_id_counter: 0,
likes_per_candidate: LookupMap::new(
StorageKey::LikesPerCandidate.try_to_vec().unwrap(),
),
added_voter_list: LookupMap::new(StorageKey::AddedVoterList.try_to_vec().unwrap()),
voted_voter_list: LookupMap::new(StorageKey::VotedVoterList.try_to_vec().unwrap()),
is_election_closed: false,
};
this
}
// initialization function
#[init]
pub fn new_default_meta(owner_id: AccountId) -> Self {
Self::new(
owner_id,
NFTContractMetadata {
spec: "nft-1.0.0".to_string(),
name: "Near Vote Contract".to_string(),
description: "This contract is design for fair election!".to_string(),
},
)
}
}
+ #[cfg(all(test, not(target_arch = "wasm32")))]
+ mod tests {
+ use near_sdk::test_utils::{accounts, VMContextBuilder};
+ use near_sdk::testing_env;
+ use std::collections::HashMap;
+
+ use super::*;
+
+ const MINT_STORAGE_COST: u128 = 100000000000000000000000;
+
+ fn get_context(predecessor_account_id: AccountId) -> VMContextBuilder {
+ let mut builder = VMContextBuilder::new();
+ builder
+ .current_account_id(accounts(0))
+ .signer_account_id(predecessor_account_id.clone())
+ .predecessor_account_id(predecessor_account_id);
+ builder
+ }
+
+ #[test]
+ fn mint_test() {
+ let mut context = get_context(accounts(1));
+ testing_env!(context.build());
+ let mut contract = Contract::new_default_meta(accounts(1).into());
+ testing_env!(context
+ .storage_usage(env::storage_usage())
+ .attached_deposit(MINT_STORAGE_COST)
+ .predecessor_account_id(accounts(1))
+ .build());
+
+ assert_eq!(contract.owner_id, accounts(1));
+
+ contract.nft_mint(
+ TokenMetadata {
+ title: None,
+ description: None,
+ media: "https...".to_string(),
+ media_CID: "Qeo...".to_string(),
+ candidate_name: None,
+ candidate_manifest: None,
+ token_kind: "candidate".to_string(),
+ token_id: None,
+ },
+ accounts(1),
+ );
+
+ assert_eq!(u128::from(contract.nft_total_supply()), 1);
+
+ let nft_info = contract.nft_tokens(None, None);
+ assert_eq!(nft_info[0].metadata.media, "https...".to_string());
+ assert_eq!(u128::from(contract.nft_supply_for_owner(accounts(1))), 1);
+ assert_eq!(
+ nft_info[0].owner_id,
+ contract.nft_tokens_for_owner(accounts(1), None, None)[0].owner_id
+ );
+ assert_eq!(
+ nft_info[0].owner_id,
+ contract.nft_tokens_for_kind("candidate".to_string(), None, None)[0].owner_id
+ );
+ }
+
+ #[test]
+ fn vote_closed_test() {
+ let mut context = get_context(accounts(1));
+ testing_env!(context.build());
+ let mut contract = Contract::new_default_meta(accounts(1).into());
+ testing_env!(context
+ .storage_usage(env::storage_usage())
+ .attached_deposit(MINT_STORAGE_COST)
+ .predecessor_account_id(accounts(1))
+ .build());
+ assert_eq!(contract.is_election_closed, false);
+
+ contract.close_election();
+ assert_eq!(contract.is_election_closed, true);
+
+ contract.reopen_election();
+ assert_eq!(contract.is_election_closed, false);
+ }
+
+ #[test]
+ fn transfer_test() {
+ let mut context = get_context(accounts(1));
+ testing_env!(context.build());
+ let mut contract = Contract::new_default_meta(accounts(1).into());
+ testing_env!(context
+ .storage_usage(env::storage_usage())
+ .attached_deposit(MINT_STORAGE_COST)
+ .predecessor_account_id(accounts(1))
+ .build());
+
+ contract.nft_mint(
+ TokenMetadata {
+ title: None,
+ description: None,
+ media: "https...".to_string(),
+ media_CID: "Qeo...".to_string(),
+ candidate_name: None,
+ candidate_manifest: None,
+ token_kind: "candidate".to_string(),
+ token_id: None,
+ },
+ accounts(1),
+ );
+
+ testing_env!(context
+ .storage_usage(env::storage_usage())
+ .attached_deposit(1)
+ .predecessor_account_id(accounts(1))
+ .build());
+
+ contract.nft_transfer(accounts(2), 0);
+
+ let nft_info = contract.nft_tokens(None, None);
+ assert_eq!(nft_info[0].owner_id, accounts(2));
+ }
+ }
3つあるテストの関数を1つずつみていきましょう。
まず最初に宣言しているget_context関数
というのはテストをしているのではなく、テストをするための仮想的なチェーン(Virtual Machine)をビルドするためのものです。
fn get_context(predecessor_account_id: AccountId) -> VMContextBuilder {
let mut builder = VMContextBuilder::new();
builder
.current_account_id(accounts(0))
.signer_account_id(predecessor_account_id.clone())
.predecessor_account_id(predecessor_account_id);
builder
}
次にかかれているmint_test関数
は文字通りmint
がうまくできているかをテストしています。
最初の部分で初期化のための関数であるnew_default_meta関数
を呼び、その後mintのための関数であるnft_mint関数
を読んだ後に、NFTの情報を見るために前回までで作成した関数を呼び思ったような挙動をしているのかを確認します。
assert_eq!()
というメソッドはひとつ目の引数と2つ目の引数が一致していなければerrorを出すようになものです。
NFTの全ての情報を確認するのは面倒なのでNFTの中の情報の一部がきちんと一致しているかをテストしています。
#[test]
fn mint_test() {
let mut context = get_context(accounts(1));
testing_env!(context.build());
let mut contract = Contract::new_default_meta(accounts(1).into());
testing_env!(context
.storage_usage(env::storage_usage())
.attached_deposit(MINT_STORAGE_COST)
.predecessor_account_id(accounts(1))
.build());
assert_eq!(contract.owner_id, accounts(1));
contract.nft_mint(
TokenMetadata {
title: None,
description: None,
media: "https...".to_string(),
media_CID: "Qeo...".to_string(),
candidate_name: None,
candidate_manifest: None,
token_kind: "candidate".to_string(),
token_id: None,
},
accounts(1),
);
assert_eq!(u128::from(contract.nft_total_supply()), 1);
let nft_info = contract.nft_tokens(None, None);
assert_eq!(nft_info[0].metadata.media, "https...".to_string());
assert_eq!(u128::from(contract.nft_supply_for_owner(accounts(1))), 1);
assert_eq!(
nft_info[0].owner_id,
contract.nft_tokens_for_owner(accounts(1), None, None)[0].owner_id
);
assert_eq!(
nft_info[0].owner_id,
contract.nft_tokens_for_kind("candidate".to_string(), None, None)[0].owner_id
);
}
この部分によってNEARをdepositしています。これはストレージを確保するためのもので、mint, transfer
のために必要になります。
testing_env!(context
.storage_usage(env::storage_usage())
.attached_deposit(MINT_STORAGE_COST)
.predecessor_account_id(accounts(1))
.build());
次のvote_closed_test関数
では投票を終了することにまつわる関数がうまく動いているかを確認しています。
fn vote_closed_test() {
let mut context = get_context(accounts(1));
testing_env!(context.build());
let mut contract = Contract::new_default_meta(accounts(1).into());
testing_env!(context
.storage_usage(env::storage_usage())
.attached_deposit(MINT_STORAGE_COST)
.predecessor_account_id(accounts(1))
.build());
assert_eq!(contract.is_election_closed, false);
contract.close_election();
assert_eq!(contract.is_election_closed, true);
contract.reopen_election();
assert_eq!(contract.is_election_closed, false);
}