Skip to main content
Blueprint provides comprehensive testing capabilities for TON smart contracts through the TON Sandbox emulator and test utilities. This guide covers everything you need to know about writing, running, and debugging tests for your smart contracts.

Quick navigation

What is TON Sandbox

TON Sandbox is a local blockchain emulator that allows you to test smart contracts without deploying them to the real TON network. It provides a complete testing environment that closely mimics the behavior of the actual TON blockchain.

Key features

Complete Transaction Emulation
  • Emulates all transaction phases
  • Accurate fee calculation and gas consumption
  • Full message routing and processing
  • Account state management
Built-in Test Utilities
  • Pre-funded treasury wallets for testing
  • Transaction matchers for Jest/Chai/
  • Coverage analysis and reporting
  • Step-by-step execution debugging

Sandbox vs real network

FeatureSandboxReal Network
SpeedInstant execution~5-15 second finality
CostFreeRequires real TON
DeterministicFully predictableNetwork conditions vary
DebuggingFull introspectionLimited visibility
State ControlComplete controlImmutable history

Sandbox limitations

While Sandbox closely emulates the real network, there are some differences to be aware of:
  • Time-dependent contracts: Sandbox time is controlled, not real-time
  • External dependencies: Cannot interact with real external contracts, but can get their state and emulate them
  • Blockchain imitation: Because there is no concept of blocks in Sandbox, things like sharding do not work.

Writing tests

Blueprint uses Jest as the default testing framework, providing powerful assertion capabilities and excellent TypeScript support.

Basic test setup

Every Blueprint project includes a test template. Here’s the standard structure:
tests/MyContract.spec.ts
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Cell, toNano } from '@ton/core';
import { MyContract } from '../wrappers/MyContract';
import '@ton/test-utils';
import { compile } from '@ton/blueprint';

describe('MyContract', () => {
    let code: Cell;

    beforeAll(async () => {
        code = await compile('MyContract');
    });

    let blockchain: Blockchain;
    let deployer: SandboxContract<TreasuryContract>;
    let myContract: SandboxContract<MyContract>;

    beforeEach(async () => {
        blockchain = await Blockchain.create();
        
        myContract = blockchain.openContract(
            MyContract.createFromConfig({}, code)
        );
        
        deployer = await blockchain.treasury('deployer');
        
        const deployResult = await myContract.sendDeploy(
            deployer.getSender(), 
            toNano('0.05')
        );

        expect(deployResult.transactions).toHaveTransaction({
            from: deployer.address,
            to: myContract.address,
            deploy: true,
            success: true,
        });
    });

    it('should deploy', async () => {
        // Contract is already deployed in beforeEach
        // Add additional deployment checks here
    });
});

Test isolation

Each test should include a fresh Blockchain instance to ensure: Test isolation
  • No state leakage between tests
  • Predictable initial conditions
  • Independent contract deployments
Clean environment
  • Fresh treasury wallets
  • Reset logical time and configuration
  • Clear transaction history
beforeEach(async () => {
    // Fresh blockchain for each test
    blockchain = await Blockchain.create();
    
    // Each test gets clean treasuries
    deployer = await blockchain.treasury('deployer');
    user = await blockchain.treasury('user');
});

Understanding transaction results

When you send a message to a contract, you receive a SendMessageResult containing:
const result = await contract.sendIncrement(user.getSender(), toNano('0.1'));

// result.transactions - Array of all transactions in the chain
// result.events - Blockchain events emitted
// result.externals - External messages generated

Transaction matchers

Blueprint provides powerful matchers for validating transactions:
expect(result.transactions).toHaveTransaction({
    from: user.address,
    to: contract.address,
    value: toNano('1'),
    op: 0x12345678, // Operation code
    success: true,
    outMessagesCount: 2, // Number of outbound messages
    deploy: false,
    body: beginCell()
        .storeUint(0, 32) // Comment op
        .storeStringTail("Hello, user!")
        .endCell()
});

Running tests

# Run all tests
npx blueprint test

# Run specific test file
npx blueprint test MyContract

# Run with coverage
npx blueprint test --coverage

# Run with gas reporting  
npx blueprint test --gas-report

Common pitfalls

Avoid These Common Mistakes
  1. Shared State: Don’t reuse blockchain instances between tests
  2. Async Issues: Always await blockchain operations
  3. Time Dependencies: Use blockchain.now for time-sensitive tests
  4. Gas Limits: Be aware of computation and action phase limits
  5. Message Ordering: Remember that message processing is sequential
  6. Treasury Reuse: Use unique seeds for different test scenarios

Debugging checklist

When tests fail, check these common issues:
  • ✅ Contract is properly deployed before testing
  • ✅ Treasury has sufficient balance for operations
  • ✅ Transaction matchers use correct field names
  • ✅ Exit codes match expected error conditions
  • ✅ Message bodies are correctly formatted
  • ✅ Time-sensitive operations account for blockchain time

Next steps

Comprehensive Testing Advanced Topics Debugging Resources Sandbox testing framework provides everything you need to thoroughly test your TON smart contracts. Start with basic deployment tests, then expand to cover all your contract’s functionality with comprehensive positive and negative test cases.
I