Skip to content

Latest commit

 

History

History
331 lines (230 loc) · 12.4 KB

tact-testing-examples.md

File metadata and controls

331 lines (230 loc) · 12.4 KB

Writing Tests for Tact

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

Transaction Success 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,
            });
    });
    

Account Status Tests

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'.

Operation Code Tests

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.

Message Counter Tests

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
        });
    })

Multi Transaction and Payload Tests

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()
        });
    })
    

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('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 Fireworks
  • 1 - The Fireworks transaction that resulted in 4 messages to the Launcher
  • 2 - Transaction on Launcher with incoming message from Fireworks, message sent with send mode = 0
  • 3 - Transaction on Launcher with incoming message from Fireworks, message sent with send mode = 1
  • 4 - Transaction on Launcher with incoming message from Fireworks, message sent with send mode = 2
  • 5 - Transaction on Launcher with incoming message from Fireworks, message sent with send mode = 128 + 32

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('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'));
        

    });