Writing test examples
This page demonstrates how to write tests for FunC contracts using the Blueprint SDK (Sandbox).
The test suites focus on the demo contract Fireworks, a smart contract that starts running with the set_first
message.
When you create a new FunC project using npm create ton@latest
, the SDK automatically generates a test file tests/contract.spec.ts
in the project directory for testing the contract:
import ...
describe('Fireworks', () => {
...
expect(deployResult.transactions).toHaveTransaction({
...
});
});
it('should deploy', async () => {
// The check is done inside beforeEach
// blockchain and fireworks are ready to use
});
Run tests using the following command:
npx blueprint test
You can specify additional options and vmLogs using blockchain.verbosity
:
blockchain.verbosity = {
...blockchain.verbosity,
blockchainLogs: true,
vmLogs: "vm_logs_full",
debugLogs: true,
print: false,
};
Direct unit tests
The Fireworks contract demonstrates different ways of sending messages in the TON Blockchain.
When you deploy the contract with the set_first
message and sufficient TON amount,
it automatically executes with primary and usable combinations of send modes.
The Fireworks contract redeploys itself, creating three entities, each with its ID and, as a result, a different smart contract address.
For clarity, define the Fireworks instances with different state_init
by ID with the following names:
- 1 - Fireworks Setter: This entity spreads different launch opcodes and can be extended to support up to four different opcodes.
- 2 - Fireworks Launcher-1: This Fireworks instance launches the first fireworks, sending messages to the launcher.
- 3 - Fireworks Launcher-2: This Fireworks instance launches the second fireworks, sending messages to the launcher.
Expand details on transactions
Index refers to the ID of a transaction in the launchResult
array.
- 0: An external request to the treasury sends an outbound message
op::set_first
with 2.5 TON to Fireworks. - 1: The Fireworks Setter contract processes a transaction with
op::set_first
, sending two outbound messages to Fireworks Launcher-1 and Fireworks Launcher-2. - 2: Fireworks Launcher-1 processes a transaction with
op::launch_first
, sending four outbound messages to the Launcher. - 3: Fireworks Launcher-2 processes a transaction with
op::launch_second
, sending one outbound message to the Launcher. - 4: The Launcher processes a transaction with an incoming message from Fireworks Launcher-1, sent with
send mode = 0
. - 5: The Launcher processes a transaction with an incoming message from Fireworks Launcher-1, sent with
send mode = 1
. - 6: The Launcher processes a transaction with an incoming message from Fireworks Launcher-1, sent with
send mode = 2
. - 7: The Launcher processes a transaction with an incoming message from Fireworks Launcher-1, sent with
send mode = 128 + 32
. - 8: The Launcher processes a transaction with an incoming message from Fireworks Launcher-2, sent with
send mode = 64
.
Each "firework" is an outbound message with a unique message body, appearing in transactions with IDs 3 and 4.
Below is a list of tests for each transaction expected to execute successfully.
Transaction ID:1 success test
This test verifies that the fireworks are successfully set by sending a transaction with a value of 2.5 TON.
This is the simplest case, in which the main goal is to confirm that the transaction result's success
property is true
.
To filter a specific transaction from the launchResult.transactions
array, you can use the most convenient fields: from
, to
, and op
. This combination retrieves only one transaction.
The transaction[ID:1] in the Fireworks Setter contract is invoked with op::set_first
and executes two outbound messages to Fireworks Launcher-1 and Fireworks Launcher-2.
it("first transaction[ID:1] should set fireworks successfully", async () => {
const launcher = await blockchain.treasury("launcher");
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano("2.5")
);
expect(launchResult.transactions).toHaveTransaction({
from: launcher.address,
to: fireworks.address,
success: true,
op: Opcodes.set_first,
});
});
Transaction ID:2 success test
This test checks if transaction[ID:2] executes successfully.
The transaction in Fireworks Launcher-1 is invoked with op::launch_first
and executes
four outbound messages to the launcher.
it("should exist a transaction[ID:2] which launches first fireworks successfully", async () => {
const launcher = await blockchain.treasury("launcher");
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano("2.5")
);
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launched_f1.address,
success: true,
op: Opcodes.launch_first,
outMessagesCount: 4,
destroyed: true,
endStatus: "non-existing",
});
printTransactionFees(launchResult.transactions);
});
When a transaction affects the state of a contract, you can specify this using the destroyed
and endStatus
fields.
The complete list of account status-related fields includes:
destroyed
:true
if the existing contract was destroyed due to executing a certain transaction. Otherwise, it isfalse
.deploy
: This custom Sandbox flag indicates whether the contract was deployed during this transaction. It istrue
if the contract was not initialized before this transaction and became initialized afterward. Otherwise, it isfalse
.oldStatus
: AccountStatus before transaction execution. Values:'uninitialized'
,'frozen'
,'active'
,'non-existing'
.endStatus
: AccountStatus after transaction execution. Values:'uninitialized'
,'frozen'
,'active'
,'non-existing'
.
Transaction ID:3 success test
This test checks if transaction[ID:3] executes successfully.
The transaction[ID:3] occurs in Fireworks Launcher-1, is invoked with op::launch_first
,
and executes four outbound messages to the launcher.
it("should exist a transaction[ID:3] which launches second fireworks successfully", async () => {
const launcher = await blockchain.treasury("launcher");
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano("2.5")
);
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launched_f2.address,
success: true,
op: Opcodes.launch_second,
outMessagesCount: 1,
});
printTransactionFees(launchResult.transactions);
});