This page demonstrate writing test for Tact contracts created in with Blueprint (Sandbox). Test suites built for demo contract Fireworks. The fireworks expect to receive message with 'Launch' command, which handled to send back leasts funds with separate message with different sendmodes.
Once a new Tact project is created via npm create ton@latest
, a test file test/contract.ts.spec
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
This test checks if the fireworks are successfully launched by sending a transaction with a value of 1 TON (converted to nanoTON) and the 'Launch' type. It then checks if the transaction was successful by validating the from
, to
, and success
fields using the transaction matcher.
it('should launch fireworks', async () => {
const launcher = await blockchain.treasury('fireworks');
console.log('launcher = ', launcher.address);
console.log('Fireworks = ', fireworks.address);
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
expect(launchResult.transactions).toHaveTransaction({
from: launcher.address,
to: fireworks.address,
success: true,
});
});
This test checks if the contract is destroyed after launching the fireworks.
it('should destroy after launching', async () => {
const launcher = await blockchain.treasury('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
expect(launchResult.transactions).toHaveTransaction({
from: launcher.address,
to: fireworks.address,
success: true,
endStatus: 'non-existing',
destroyed: true
});
});
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'
.
This test shows how to check whether the operation code (op code) of incoming message is equal to the expected op code.
it('should be correct Launch op code for the launching', async () => {
const launcher = await blockchain.treasury('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
expect(launchResult.transactions).toHaveTransaction({
from: launcher.address,
to: fireworks.address,
success: true,
op: 0xa911b47f // 'Launch' op code
});
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launcher.address,
success: true,
op: 0 // 0x00000000 - comment op code
});
});
For Tact contracts, crc32 representation could be found in the project build directory, autogenerated with build contract.md file. Read more about crc32 and op codes in the TON documentation.
This test checks if the correct number of messages are sent in the transaction.
it('should send 4 messages to wallet', async() => {
const launcher = await blockchain.treasury('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
expect(launchResult.transactions).toHaveTransaction({
from: launcher.address,
to: fireworks.address,
success: true,
outMessagesCount: 4
});
})
This test checks if the fireworks contract sends multiple messages with comments correctly. The body field contains a Cell that is built with @ton/core primitives.
it('fireworks contract should send msgs with comments', async() => {
const launcher = await blockchain.treasury('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 0").endCell() // 0x00000000 comment opcode and encoded comment
});
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 1").endCell()
});
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 2").endCell()
});
expect(launchResult.transactions).toHaveTransaction({
from: fireworks.address,
to: launcher.address,
success: true,
body: beginCell().storeUint(0,32).storeStringTail("send mode = 128 + 32").endCell()
});
})
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('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
console.log(printTransactionFees(launchResult.transactions));
});
For instance, in case of launchResult
the following table will be printed:
┌─────────┬──────────────┬────────────────┬────────────────┬────────────────┬────────────────┬───────────────┬────────────┬────────────────┬──────────┬────────────┐
│ (index) │ op │ valueIn │ valueOut │ totalFees │ inForwardFee │ outForwardFee │ outActions │ computeFee │ exitCode │ actionCode │
├─────────┼──────────────┼────────────────┼────────────────┼────────────────┼────────────────┼───────────────┼────────────┼────────────────┼──────────┼────────────┤
│ 0 │ 'N/A' │ 'N/A' │ '1 TON' │ '0.003935 TON' │ 'N/A' │ '0.001 TON' │ 1 │ '0.001937 TON' │ 0 │ 0 │
│ 1 │ '0xa911b47f' │ '1 TON' │ '0.980644 TON' │ '0.016023 TON' │ '0.000667 TON' │ '0.005 TON' │ 4 │ '0.014356 TON' │ 0 │ 0 │
│ 2 │ '0x0' │ '0.098764 TON' │ '0 TON' │ '0.000309 TON' │ '0.000825 TON' │ 'N/A' │ 0 │ '0.000309 TON' │ 0 │ 0 │
│ 3 │ '0x0' │ '0.1 TON' │ '0 TON' │ '0.000309 TON' │ '0.000825 TON' │ 'N/A' │ 0 │ '0.000309 TON' │ 0 │ 0 │
│ 4 │ '0x0' │ '0.098764 TON' │ '0 TON' │ '0.000309 TON' │ '0.000825 TON' │ 'N/A' │ 0 │ '0.000309 TON' │ 0 │ 0 │
│ 5 │ '0x0' │ '0.683116 TON' │ '0 TON' │ '0.000309 TON' │ '0.000862 TON' │ 'N/A' │ 0 │ '0.000309 TON' │ 0 │ 0 │
└─────────┴──────────────┴────────────────┴────────────────┴────────────────┴────────────────┴───────────────┴────────────┴────────────────┴──────────┴────────────┘
index - is an ID of a transaction in the launchResult array.
0
- External request to the treasury (the Launcher) that resulted in a message to Fireworks1
- The Fireworks transaction that resulted in 4 messages to the Launcher2
- Transaction on Launcher with incoming message from Fireworks, message sent withsend mode = 0
3
- Transaction on Launcher with incoming message from Fireworks, message sent withsend mode = 1
4
- Transaction on Launcher with incoming message from Fireworks, message sent withsend mode = 2
5
- Transaction on Launcher with incoming message from Fireworks, message sent withsend mode = 128 + 32
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('fireworks');
const launchResult = await fireworks.send(
launcher.getSender(),
{
value: toNano('1'),
},
{
$$type: 'Launch',
}
);
//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);
//The check, if Compute Phase and Action Phase fees exceed 1 TON
expect(computeFee + actionFee).toBeLessThan(toNano('1'));
});