Skip to content
Retr0 Academy
NEAR Smart Contracts

NEAR Smart Contracts · 2 min read

Writing and Testing a Contract

Build a small greeting contract in near-sdk-rs or near-sdk-js, then test it locally before you touch testnet.

A NEAR contract is just WebAssembly stored on an account. Rust (near-sdk) is the usual choice for production. JavaScript (near-sdk-js) is fine for prototypes. Both compile to Wasm and expose the same kind of view and change methods.

Many fast unit tests, fewer sandbox tests, a final testnet check.
Many fast unit tests, fewer sandbox tests, a final testnet check.

Rust with near-sdk 5

#[near(contract_state)] marks the struct that is serialized to storage. Default is used if you deploy without an init call. Methods on #[near] impl become public ABI methods.

rust

use near_sdk::{near, env};
 
#[near(contract_state)]
pub struct Contract {
    greeting: String,
}
 
impl Default for Contract {
    fn default() -> Self {
        Self { greeting: "Hello".to_string() }
    }
}
 
#[near]
impl Contract {
    pub fn get_greeting(&self) -> String {
        self.greeting.clone()
    }
 
    pub fn set_greeting(&mut self, greeting: String) {
        let who = env::predecessor_account_id();
        env::log_str(&format!("{who} set greeting"));
        self.greeting = greeting;
    }
}

get_greeting is a view method because it only takes &self. set_greeting is a change method because it takes &mut self and writes state.

JavaScript with near-sdk-js

js

import { NearBindgen, near, call, view } from "near-sdk-js";
 
@NearBindgen({})
export class HelloNear {
  greeting = "Hello";
 
  @view({})
  get_greeting() {
    return this.greeting;
  }
 
  @call({})
  set_greeting({ greeting }) {
    near.log(`${near.predecessorAccountId()} set greeting`);
    this.greeting = greeting;
  }
}

Unit tests

near-sdk ships VMContextBuilder so you can unit-test without a network. Integration tests that deploy Wasm use near-workspaces and cargo test.

rust

#[cfg(test)]
mod tests {
    use super::*;
    use near_sdk::test_utils::{accounts, VMContextBuilder};
    use near_sdk::testing_env;
 
    fn context(predecessor: usize) {
        let mut b = VMContextBuilder::new();
        b.predecessor_account_id(accounts(predecessor));
        testing_env!(b.build());
    }
 
    #[test]
    fn default_then_set() {
        let mut c = Contract::default();
        assert_eq!(c.get_greeting(), "Hello");
        context(1);
        c.set_greeting("Howdy".into());
        assert_eq!(c.get_greeting(), "Howdy");
    }
}

From the contract folder: cargo test. When you are ready to deploy, the next lessons cover collections, tokens, and near-cli-rs on testnet.

Call a live greeting-style view method
<h1>View call from the browser</h1>
<p>This talks to a public example contract on testnet. No wallet, no gas.</p>
<button type="button" id="go">Read get_greeting</button>
<pre id="out">Press the button.</pre>

Edit this example in the Playground.

Open in Playground

When the tests pass, the last lesson in this course covers cargo near build and near deploy on testnet.

Exercise

Clone or paste the Rust greeting contract, add a second test that calls set_greeting from a different predecessor, and run cargo test.