Skip to main content

Unit Test Voting Smart Contract Logic

It is advice to create a unit test for the smart contract before it is deployed to the aelf blockchain. The unit test not just help us to verify the smart contract is free from bugs, it is also gives us the opportunity to review our code before it is deployed.

In this step, we will be writing the unit test of our Voting Smart Contract in the test/BuildersDAOTests.cs file!

Proceed to overwrite the existing contents of test/BuildersDAOTests.cs file with the following code snippet.

Implementing Unit Test for Initialize

The Initialize method is to initialize the smart contract.

In this method, we pass a empty parameter, and initialize a proposal called Proposal #1.

Positive cases

We call the initialize once, and get corresponding proposal by calling GetProposal with proposalId, then verify whether the title of proposal is Proposal #1.

We can use ShouldBe method to judge the the value of proposal.Title.

[Fact]
public async Task InitializeTest_Success()
{
await BuildersDAOStub.Initialize.SendAsync(new Empty());
var proposal = await BuildersDAOStub.GetProposal.CallAsync(new StringValue {Value = "0"});
proposal.Title.ShouldBe("Proposal #1");
}

Negative cases

We call the initialize twice, when we make the second call, it will throw an exception containing already initialized.

We can use ShouldContain method to judge the the message content of Exception.

[Fact]
public async Task InitializeTest_Duplicate()
{
await BuildersDAOStub.Initialize.SendAsync(new Empty());
var executionResult = await BuildersDAOStub.Initialize.SendWithExceptionAsync(new Empty());
executionResult.TransactionResult.Error.ShouldContain("already initialized");
}

Implementing Unit Test for JoinDAO

The JoinDAO method is to join the StackUpDAO so that users can use the subsequent methods,

In this method, we pass the address as a parameter. Then call the method, and set State.Members[input] = true.

tip

Note that in aelf unit tests, we can directly use the Accounts[x] approach to obtain a test account for conducting unit tests, which we will also utilize here.

danger

If you haven't completed the implementation of JoinDAO method, when you run this unit test method, it will fail. The implementation code being referenced is provided in the latter part; you can refer to it, and check if your implementation matches the reference code.

Positive cases

We call the JoinDAO with Account[1] and Account[2], and get the value of member exist by calling GetMemberExist with Address. Then verify whether the exist result is true.

We can use ShouldBe method to judge the the value of exist.

[Fact]
public async Task JoinDAOTest_Success()
{
await BuildersDAOStub.Initialize.SendAsync(new Empty());
await BuildersDAOStub.JoinDAO.SendAsync(Accounts[1].Address);
await BuildersDAOStub.JoinDAO.SendAsync(Accounts[2].Address);
var exist1 = await BuildersDAOStub.GetMemberExist.CallAsync(Accounts[1].Address);
var exist2 = await BuildersDAOStub.GetMemberExist.CallAsync(Accounts[2].Address);
exist1.Value.ShouldBe(true);
exist2.Value.ShouldBe(true);
}

Negative cases

We call the JoinDAO with Account[1] twice, when we make the second call, it will throw an exception containing Member is already in the DAO.

We can use ShouldContain method to judge the the message content of Exception.

[Fact]
public async Task JoinDAOTest_Duplicate()
{
await BuildersDAOStub.Initialize.SendAsync(new Empty());
await BuildersDAOStub.JoinDAO.SendAsync(Accounts[1].Address);
var executionResult = await BuildersDAOStub.JoinDAO.SendWithExceptionAsync(Accounts[1].Address);
executionResult.TransactionResult.Error.ShouldContain("Member is already in the DAO");
}

Implementing Unit Test for CreateProposal

The CreateProposal method is to create a proposal.

We have separately written a CreateProposal method, where users pass an address as a parameter to create proposal, and return this proposal.

Positive cases

We call the previous method to mock the data that we need, and call CreateProposal method with Accounts[1].Address. Then verify whether the value of proposal.Title is proposal test, and the value of proposal.Id is 1.

We can use ShouldBe method to judge the the value of proposal.Title and proposal.Id.

[Fact]
public async Task CreateProposalTest_Success()
{
await JoinDAOTest_Success();
var proposal = await CreateMockProposal(Accounts[1].Address);
proposal.Title.ShouldBe("mock_proposal");
proposal.Id.ShouldBe("1");
}

private async Task<Proposal> CreateMockProposal(Address creator)
{
var createProposalInput = new CreateProposalInput
{
Creator = creator,
Description = "mock_proposal_desc",
Title = "mock_proposal",
VoteThreshold = 1
};
var proposal = await BuildersDAOStub.CreateProposal.SendAsync(createProposalInput);
return proposal.Output;
}

Negative cases

We will call CreateProposal with Account[2].Address, it will throw an exception containing Only DAO members can create proposals.

We can use ShouldContain method to judge the the message content of Exception.

[Fact]
public async Task CreateProposalTest_NoPermission()
{
await JoinDAOTest_Success();
try
{
await CreateMockProposal(Accounts[2].Address);
}
catch (Exception e)
{
e.Message.ShouldContain("Only DAO members can create proposals");
}
}

Implementing Unit Test for VoteOnProposal

The VoteOnProposal method is to vote for proposal.

Positive cases

We will call the previous method to mock the data we need. Then call the VoteOnProposal method with Account[1] and Account[2], and Account[1] voted for the Vote = true option, Account[2] voted for the Vote = false option, and get proposal results by calling GetProposal with proposalId. Then verify whether the results are the same as input.

We can use ShouldContain method to judge the the values of proposalResult.YesVotes and proposalResult.NoVotes.

[Fact]
public async Task VoteOnProposalTest_Success()
{
await JoinDAOTest_Success();
var proposal = await CreateMockProposal(Accounts[1].Address);
var voteInput1 = new VoteInput
{
ProposalId = proposal.Id,
Vote = true,
Voter = Accounts[1].Address
};
var voteInput2 = new VoteInput
{
ProposalId = proposal.Id,
Vote = false,
Voter = Accounts[2].Address
};
await BuildersDAOStub.VoteOnProposal.SendAsync(voteInput1);
await BuildersDAOStub.VoteOnProposal.SendAsync(voteInput2);
var proposalResult = BuildersDAOStub.GetProposal.CallAsync(new StringValue{Value = proposal.Id}).Result;
proposalResult.YesVotes.ShouldContain(Accounts[1].Address);
proposalResult.NoVotes.ShouldContain(Accounts[2].Address);
}

Negative cases

First case is we will call VoteOnProposal with Account[3], it will throw an exception containing Only DAO members can vote. Another case is call VoteOnProposal with the wrong ProposalId, it will throw an exception containing Proposal not found.

We can use ShouldContain method to judge the the message content of Exception.

[Fact]
public async Task VoteOnProposalTest_NoPermission()
{
await JoinDAOTest_Success();
var proposal = await CreateMockProposal(Accounts[1].Address);
var voteInput1 = new VoteInput
{
ProposalId = proposal.Id,
Vote = true,
Voter = Accounts[3].Address
};
var executionResult = await BuildersDAOStub.VoteOnProposal.SendWithExceptionAsync(voteInput1);
executionResult.TransactionResult.Error.ShouldContain("Only DAO members can vote");
}

[Fact]
public async Task VoteOnProposalTest_NoProposal()
{
await JoinDAOTest_Success();
var proposal = await CreateMockProposal(Accounts[1].Address);
var voteInput1 = new VoteInput
{
ProposalId = "123",
Vote = true,
Voter = Accounts[1].Address
};
var executionResult = await BuildersDAOStub.VoteOnProposal.SendWithExceptionAsync(voteInput1);
executionResult.TransactionResult.Error.ShouldContain("Proposal not found");
}

Next, navigate to the test directory and run dotnet test to execute the unit test cases.

Check the result of the unit test. You should see the following output from terminal if you haven't implemented all the methods:

result