4. Test the multi-contract flow
Write integration tests for Poll and Voter, use the Test UI to see message traces, and expose the duplicate-vote bug.
Set up the test file
Create tests/poll.test.tolk:
import "@acton/emulation/network"
import "@acton/emulation/testing"
import "@acton/testing/expect"
import "@contracts/types"
import "@wrappers/Poll.gen"
fun setupTest(): (Poll, Treasury, Treasury, Treasury) {
val owner = testing.treasury("owner");
val alice = testing.treasury("alice");
val bob = testing.treasury("bob");
val poll = Poll.fromStorage(PollStorage {
owner: owner.address,
option0: 0,
option1: 0,
});
val res = poll.deploy(owner.address, { value: ton("0.5") });
expect(res).toHaveSuccessfulDeploy({ to: poll.address });
return (poll, owner, alice, bob);
}setupTest() deploys Poll and returns four handles. Every test calls setupTest() at the top to get a fresh contract — this avoids shared-state bugs common in TypeScript tests.
testing.treasury("name") creates a funded test account. Each unique name produces a deterministic address.
Test a successful vote
get fun `test alice votes option 0`() {
val (poll, _, alice, _) = setupTest();
val res = poll.sendVote(alice.address, 0, { value: ton("0.1") });
expect(res).toHaveAllSuccessfulTxs();
val (opt0, opt1) = poll.results();
expect(opt0).toEqual(1);
expect(opt1).toEqual(0);
}acton test --filter "alice votes option 0"$ acton test --filter "alice votes option 0"
Compiling contracts
Finished in 612.44µs
Running tests
TEST <root>/poll
> tests/poll.test.tolk (1 test)
✓ alice votes option 0 18ms
✓ 1 passed in 1 fileTest independent votes from two wallets
get fun `test alice and bob vote for different options`() {
val (poll, _, alice, bob) = setupTest();
val res0 = poll.sendVote(alice.address, 0, { value: ton("0.1") });
expect(res0).toHaveAllSuccessfulTxs();
val res1 = poll.sendVote(bob.address, 1, { value: ton("0.1") });
expect(res1).toHaveAllSuccessfulTxs();
val (opt0, opt1) = poll.results();
expect(opt0).toEqual(1);
expect(opt1).toEqual(1);
}Each voter has an independent Voter contract, so neither interferes with the other.
Explore traces in the Test UI
acton test --ui$ acton test --ui
Test UI started at http://127.0.0.1:12344The command will automatically open the UI in your default browser.
On the transactions tab, the first three transactions are wallet deployments, the fourth is the Poll deployment, and the last is the voting logic:
alice → PollwithVote(option=0)— Poll incrementsoption0and sends to VoterPoll → Voterwith the sameVote(option=0)— Voter setshasVoted = true
Press Ctrl+C in the terminal to stop the UI server.
Expose the duplicate-vote bug
get fun `test alice cannot vote twice`() {
val (poll, _, alice, _) = setupTest();
val res1 = poll.sendVote(alice.address, 0, { value: ton("0.1") });
expect(res1).toHaveAllSuccessfulTxs();
// Alice votes a second time.
val res2 = poll.sendVote(alice.address, 0, { value: ton("0.1") });
// After fixing the bounce handler on page 5, this will stay at (1, 0).
// Right now Poll does not undo the optimistic increment, so option0 is wrongly 2.
val (opt0, _) = poll.results();
expect(opt0).toEqual(1); // fails until page 5
}acton test$ acton test
Compiling contracts
Finished in 601.33µs
Running tests
TEST <root>/poll
> tests/poll.test.tolk (3 tests)
✓ alice votes option 0 18ms
✓ alice and bob vote for different options 22ms
✗ alice cannot vote twice 16ms
└─ Error: expect(actual).toEqual(expected)
(
2,
1
)
└─ at tests/poll.test.tolk:61:5
✗ 1 failed in 1 fileThe third test fails as expected. Poll increments option0 to 2 even though Voter correctly rejected the second vote. The bounce signal from Voter triggers Poll's onBouncedMessage but the handler currently does nothing.
Reopen the Test UI and select the failing test. In the transactions tab, choose the latest transaction. The trace would show that the Vote transaction failed with exit code 101, and the Poll transaction received a bounced message, which is marked by a dotted line and ignored by the Poll contract.


Checkpoint
Three tests exist: two passing, one deliberately failing. Leave the failing test in place — it passes once the bounce handler is added on the next page.
Commands introduced: acton test, acton test --filter, acton test --ui
Last updated on