Writing Tests Examples
This page demonstrates how to write test for FunC contracts created in with Blueprint SDK (Sandbox).
Test suites built for demo contract fireworks. The fireworks is a smart contract which initially run via set_first
message.
Once a new FunC project is created via npm create ton@latest
, a test file tests/contract.spec.ts
will be autogenerated 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
});
Running tests using the following command:
npx blueprint test
Additional options and vmLogs may be specified with blockchain.verbosity
:
blockchain.verbosity = {
...blockchain.verbosity,
blockchainLogs: true,
vmLogs: 'vm_logs_full',
debugLogs: true,
print: false,
}
Direct Unit Tests
Fireworks demonstrate different operating with sending messages in the TON Blockchain.
Once you deploy this with message set_first
with enough TON amount, it will be automatically executed with primary and usable combinations of send modes.
Fireworks redeployed itself, as result it will be created 3 entities of Fireworks entities, while each of entity has own ID(keep it in storage) and, as a result, different Smart Contract Address.
For clearness define different by ID Fireworks instances (different state_init
) with the following names:
- 1 - Fireworks setter - The entity that spread different launch op codes. Could be extended up to four different opcodes.
- 2 - Fireworks launcher-1 - The Fireworks instance, which launch first fireworks, means messages will be sent to the launcher.
- 3 - Fireworks launcher-2 - The Fireworks instance, which launch second fireworks, means messages will be sent launcher.
Expand details on transactions
index - is an ID of a transaction in the launchResult
array.
0
- External request to the treasury (the Launcher) that resulted with a outbound messageop::set_first
with 2.5 to fireworks1
- The transaction in Fireworks setter contract invoked withop::set_first
and executed with two outbound messages to the Fireworks Launcher-1 and Fireworks Launcher-22
- The transaction in the Fireworks launcher 1 invoked withop::launch_first
, and executed with four outbound messages to the Launcher.3
- The transaction in the Fireworks launcher 2 invoked withop::launch_second
, and executed with a outbound message to the Launcher.4
- Transaction in the Launcher with incoming message from the Fireworks launcher 1. This message sent withsend mode = 0
.5
- Transaction in the Launcher with incoming message from the Fireworks launcher 1. This message sent withsend mode = 1
6
- Transaction in the Launcher with incoming message from the Fireworks launcher 1. This message sent withsend mode = 2
7
- Transaction in the Launcher with incoming message from the Fireworks launcher 1. This message sent withsend mode = 128 + 32
8
- Transaction in the Launcher with incoming message from the Fireworks launcher 2. This message sent withsend mode = 64
Each 'firework' - is outbound message with a unique message body appears in transactions with ID:3 and ID:4.
Bellow the list of test for each transaction expected successfully executed. Transaction[ID:0] External request to the treasury (the Launcher) that resulted with a outbound message op::set_first
with 2.5 to fireworks. In case you will deploy Fireworks to the blockchain launcher is your wallet.
Transaction ID:1 Success Test
This test checks if the fireworks are successfully set by sending a transaction with a value of 2.5 TON. This is the simplest case, the main purpose here to assert result of transaction success property to true.
To filter certain transaction from the launhcResult.transactions
array, we can use the most convince fields.
With
from
(contract sender address), to
(contract destination address), op
(Op code value) - we will retrieve only one transaction for this combination.
The transaction[ID:1] in Fireworks Setter contract invoked with op::set_first
and executed with two outbound messages to the 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 the transaction[ID:2] executed successfully.
The transaction in the Fireworks launcher 1 invoked with op::launch_first
, and executed with four outbound messages to the Launcher.
it('should exist a transaction[ID:2] which launch 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);
});
In cases, when transaction should affect the state of contract, it is possible to specify this with destroyed
, endStatus
fields.
The full list of Account Status related fields:
destroyed
-true
- if the existing contract was destroyed due to executing a certain transaction. Otherwise -false
.deploy
- Custom Sandbox flag that indicates whether the contract was deployed during this transaction.true
if contract before this transaction was not initialized and after this transaction became initialized. Otherwise -false
.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 the transaction[ID:3] executed successfully.
The transaction[ID:3] carries out in the Fireworks launcher 1, invokes with op::launch_first
, and executes with four outbound messages to the Launcher.
it('should exist a transaction[ID:3] which launch 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);
});
Transaction ID:4 Success Test
This test checks if the transaction[ID:4] executed successfully.
Transaction[ID:4] carries out in the Launcher(Deploy Wallet) with incoming message from the Fireworks launcher 1. This message sent with send mode = 0
in the Transaction[ID:2].
it('should exist a transaction[ID:4] with a comment send mode = 0', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
expect(launchResult.transactions).toHaveTransaction({
from: launched_f1.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 0").endCell() // 0x00000000 comment opcode and encoded comment
});
})
Transaction ID:5 Success Test
This test checks if the transaction[ID:5] executed successfully.
Transaction[ID:5] carries out in the Launcher with incoming message from the Fireworks launcher 1. This message sent with send mode = 1
it('should exist a transaction[ID:5] with a comment send mode = 1', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
expect(launchResult.transactions).toHaveTransaction({
from: launched_f1.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 1").endCell() // 0x00000000 comment opcode and encoded comment
});
})
Transaction ID:6 Success Test
This test checks if the transaction[ID:6] executed successfully.
The transaction[ID:6] carries out in the Launcher with incoming message from the Fireworks launcher 1. This message sent with send mode = 2
it('should exist a transaction[ID:6] with a comment send mode = 2', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
expect(launchResult.transactions).toHaveTransaction({
from: launched_f1.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 2").endCell() // 0x00000000 comment opcode and encoded comment
});
})
Transaction ID:7 Success Test
This test checks if the transaction[ID:7] executed successfully.
The transaction[ID:7] carries out in the Launcher with incoming message from the Fireworks launcher 1. This message sent with send mode = 128 + 32
it('should exist a transaction[ID:7] with a comment send mode = 32 + 128', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
expect(launchResult.transactions).toHaveTransaction({
from: launched_f1.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 32 + 128").endCell() // 0x00000000 comment opcode and encoded comment
});
})
Transaction ID:8 Success Test
This test checks if the transaction[ID:8] executed successfully.
The transaction[ID:8] carries out in the Launcher with incoming message from the Fireworks launcher 2. This message sent with send mode = 64
it('should exist a transaction[ID:8] with a comment send mode = 64', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
expect(launchResult.transactions).toHaveTransaction({
from: launched_f2.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send_mode = 64").endCell() // 0x00000000 comment opcode and encoded comment
});
})
Printing and Reading Transaction Fees
During the test, reading the details about fees can be useful for optimizing the contract. The printTransactionFees function prints the entire transaction chain in a convenient manner."
it('should be executed and print fees', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
console.log(printTransactionFees(launchResult.transactions));
});
For instance, in case of launchResult
the following table will be printed:
(index) | op | valueIn | valueOut | totalFees | outActions |
---|---|---|---|---|---|
0 | 'N/A' | 'N/A' | '2.5 TON' | '0.010605 TON' | 1 |
1 | '0x5720cfeb' | '2.5 TON' | '2.185812 TON' | '0.015836 TON' | 2 |
2 | '0x6efe144b' | '1.092906 TON' | '1.081142 TON' | '0.009098 TON' | 4 |
3 | '0xa2e2c2dc' | '1.092906 TON' | '1.088638 TON' | '0.003602 TON' | 1 |
4 | '0x0' | '0.099 TON' | '0 TON' | '0.000309 TON' | 0 |
5 | '0x0' | '0.1 TON' | '0 TON' | '0.000309 TON' | 0 |
6 | '0x0' | '0.099 TON' | '0 TON' | '0.000309 TON' | 0 |
7 | '0x0' | '0.783142 TON' | '0 TON' | '0.000309 TON' | 0 |
8 | '0x0' | '1.088638 TON' | '0 TON' | '0.000309 TON' | 0 |
index - is an ID of a transaction in the launchResult
array.
0
- External request to the treasury (the Launcher) that resulted in a messageop::set_first
to Fireworks1
- The Fireworks transaction that resulted in 4 messages to the Launcher2
- Transaction on Launched Fireworks - 1 from the Launcher, message sent withop::launch_first
op code.2
- Transaction on Launched Fireworks - 2 from the Launcher, message sent withop::launch_second
op code.4
- Transaction on Launcher with incoming message from the Launched Fireworks - 1, message sent withsend mode = 0
5
- Transaction on Launcher with incoming message from the Launched Fireworks - 1, message sent withsend mode = 1
6
- Transaction on Launcher with incoming message from the Launched Fireworks - 1, message sent withsend mode = 2
7
- Transaction on Launcher with incoming message from the Launched Fireworks - 1, message sent withsend mode = 128 + 32
8
- Transaction on Launcher with incoming message from the Launched Fireworks - 2, message sent withsend mode = 64
Transaction Fees Tests
This test verifies whether the transaction fees for launching the fireworks are as expected. It is possible to define custom assertions for different parts of commission fees.
it('should be executed with expected fees', async() => {
const launcher = await blockchain.treasury('launcher');
const launchResult = await fireworks.sendDeployLaunch(
launcher.getSender(),
toNano('2.5'),
);
//totalFee
console.log('total fees = ', launchResult.transactions[1].totalFees);
const tx1 = launchResult.transactions[1];
if (tx1.description.type !== 'generic') {
throw new Error('Generic transaction expected');
}
//computeFee
const computeFee = tx1.description.computePhase.type === 'vm' ? tx1.description.computePhase.gasFees : undefined;
console.log('computeFee = ', computeFee);
//actionFee
const actionFee = tx1.description.actionPhase?.totalActionFees;
console.log('actionFee = ', actionFee);
if ((computeFee == null || undefined) ||
(actionFee == null || undefined)) {
throw new Error('undefined fees');
}
//The check, if Compute Phase and Action Phase fees exceed 1 TON
expect(computeFee + actionFee).toBeLessThan(toNano('1'));
});