Skip to main content
While developing your smart contract, you’ll want to test that it works as expected and securely. NEAR provides tools to help you carry out comprehensive testing. There are two main types of tests:

Unit Tests

Test methods individually in the contract’s language, executed locally

Integration Tests

Test contract behavior in a realistic environment using Workspaces
We recommend implementing both types of tests to catch different types of errors and ensure your code works as intended.

Unit Tests

Unit tests allow you to test individual functions within your contract in isolation.

Rust Unit Tests

use near_sdk::{near, env};

#[near(contract_state)]
pub struct Counter {
    count: u64,
}

#[near]
impl Counter {
    #[init]
    pub fn new() -> Self {
        Self { count: 0 }
    }

    pub fn increment(&mut self) {
        self.count += 1;
    }

    pub fn get_count(&self) -> u64 {
        self.count
    }
}

JavaScript Unit Tests

import { NearBindgen, call, view } from 'near-sdk-js';

@NearBindgen({})
class Counter {
  count = 0;

  @call({})
  increment() {
    this.count += 1;
  }

  @view({})
  get_count() {
    return this.count;
  }
}

Integration Tests

Integration tests deploy your contract in a sandbox environment or testnet, allowing you to test realistic interactions.

NEAR Workspaces

Workspaces is a testing framework that enables:
  • Creating test accounts
  • Deploying contracts to sandbox
  • Simulating user interactions
  • Testing cross-contract calls
  • Time travel (fast-forwarding blockchain time)
  • State manipulation
Available in Rust and TypeScript.
By default, Workspaces runs a local sandbox. Tests are fast and don’t require testnet connectivity.

Setting Up Workspaces

Add to Cargo.toml:
[dev-dependencies]
near-workspaces = "0.10"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
Create test file tests/test_basics.rs:
use near_workspaces::{Account, Contract};
use serde_json::json;

#[tokio::test]
async fn test_contract() -> Result<(), Box<dyn std::error::Error>> {
    let worker = near_workspaces::sandbox().await?;
    let wasm = near_workspaces::compile_project("./").await?;

    let contract = worker.dev_deploy(&wasm).await?;
    let account = worker.dev_create_account().await?;

    // Test your contract
    Ok(())
}

Common Testing Patterns

Deploying and Initializing

#[tokio::test]
async fn test_init() -> Result<(), Box<dyn std::error::Error>> {
    let worker = near_workspaces::sandbox().await?;
    let wasm = near_workspaces::compile_project("./").await?;

    // Deploy contract
    let contract = worker.dev_deploy(&wasm).await?;

    // Initialize
    contract.call("init")
        .args_json(json!({
            "owner": contract.id()
        }))
        .transact()
        .await?;

    Ok(())
}

Calling Functions

// Call a change method
let result = account
    .call(contract.id(), "increment")
    .args_json(json!({}))
    .transact()
    .await?;

assert!(result.is_success());

// View method
let count: u64 = contract
    .view("get_count")
    .args_json(json!({}))
    .await?
    .json()?;

assert_eq!(count, 1);

Testing with Deposits

use near_sdk::NearToken;

let result = account
    .call(contract.id(), "donate")
    .args_json(json!({}))
    .deposit(NearToken::from_near(5))
    .transact()
    .await?;

assert!(result.is_success());

Creating Test Accounts

// Create a development account
let account = worker.dev_create_account().await?;

// Create a subaccount
let alice = contract
    .as_account()
    .create_subaccount("alice")
    .initial_balance(NearToken::from_near(10))
    .transact()
    .await?
    .into_result()?;

Advanced Testing Features

Time Travel

Fast-forward the blockchain to test time-dependent functionality:
// Fast forward 100 blocks
worker.fast_forward(100).await?;

// Check auction has ended
let ended: bool = contract
    .view("is_auction_ended")
    .args_json(json!({}))
    .await?
    .json()?;

assert!(ended);

Patch State

Modify contract state directly (sandbox only):
use near_workspaces::network::Sandbox;

let mut contract_state = contract.view_state().await?;
// Modify state
contract_state.insert("key".as_bytes().to_vec(), "value".as_bytes().to_vec());

worker.patch_state(contract.id(), &contract_state).await?;

Checking Logs

let result = account
    .call(contract.id(), "log_message")
    .transact()
    .await?;

println!("Logs: {:?}", result.logs());
assert!(result.logs().contains(&"Hello World".to_string()));

Testing on Testnet

Run tests against actual testnet:
#[tokio::test]
async fn test_on_testnet() -> Result<(), Box<dyn std::error::Error>> {
    let worker = near_workspaces::testnet().await?;
    
    // Load account from credentials
    let account = worker
        .import_account("your-account.testnet")
        .await?;

    // Deploy and test on testnet
    Ok(())
}

Example: Testing Hello NEAR

Complete example testing the Hello NEAR contract:
use near_workspaces::{Account, Contract};
use serde_json::json;

#[tokio::test]
async fn test_hello_near() -> Result<(), Box<dyn std::error::Error>> {
    let worker = near_workspaces::sandbox().await?;
    let wasm = near_workspaces::compile_project("./").await?;

    let contract = worker.dev_deploy(&wasm).await?;
    let alice = worker.dev_create_account().await?;

    // Test default greeting
    let greeting: String = contract
        .view("get_greeting")
        .await?
        .json()?;
    assert_eq!(greeting, "Hello");

    // Change greeting
    alice
        .call(contract.id(), "set_greeting")
        .args_json(json!({ "greeting": "Howdy" }))
        .transact()
        .await?;

    // Verify change
    let greeting: String = contract
        .view("get_greeting")
        .await?
        .json()?;
    assert_eq!(greeting, "Howdy");

    Ok(())
}

Best Practices

Always test boundary conditions:
  • Empty inputs
  • Maximum values
  • Negative scenarios
  • Unauthorized access
Verify that state changes persist correctly:
  • Check values before and after
  • Test multiple sequential operations
  • Verify rollback behavior
Test contract interactions:
  • Successful calls
  • Failed calls and callbacks
  • Gas handling
  • Deposit handling
Ensure errors are handled properly:
  • Invalid arguments
  • Insufficient funds
  • Unauthorized calls
  • State validation

Resources

Workspaces Rust

Rust testing framework documentation

Workspaces JS

JavaScript testing framework documentation

Example Tests

View example projects with tests

Security Testing

Security testing best practices

Next Steps

Security

Learn security best practices

Deploy

Deploy your tested contract

Upgrade

Learn about contract updates

Examples

Explore more examples

Build docs developers (and LLMs) love