Test Smart Contract Functionality with Ethers and Hardhat

Share this video with your friends

Social Share Links

Send Tweet
Published 2 years ago
Updated 2 years ago

Tests are important, as always but it may be more important when you are developing an smart contract. Why? Because the blockchain is immutable.

The main difference between Solidity development and other languages or environment is that after the implementation of an smart contract in the Ethereum network the contract is immutable, meaning that it will never be modified or updated again.

The first code that get's deployed is the one that will remains unchanged for the rest of time. This is because one the main concerns of Solidity is security. If there's a flaw in your contract code, there is no way to patch it later. You would have to tell to the users of the contract to use a different contract (identified by an address) to get the fix.

Sounds uncomfortable right? But, believe it or not, this is a feature. The code that your wrote it becomes a law. IF you read the code of an smart contract (and verified that that code is actually the one deployed) you can be sure that every time you call a function it will behave exactly as the code says it.

So you need to be sure before deploy and the best way to get that confidence is by testing.

Hardhat (the development environment of choice for this course) offers the tools to test the smart contract using Javascript or Typescript by leveraging the power of Ethers.js to interact with the Ethereum network and Waffle, a testing library built on top of Ethere.js

You already dip your toes on a bit of testing code, now let's add a more robust test to check that the contract behaves as expected.

Instructor: [0:00] Now, you have pieces of the smart contract ready to be used, but remember, testing a smart contract is very important in the process of creating one, because after the deployment, it cannot be changed. Let's write tests for your smart contract. [0:23] Open the tipjar.js file and start adding new test. The first test to create is to check that the tip is successfully sent and that the total of new tips is bigger than before. This will be an asynchronous function. Let's retrieve some addresses for the operations using ethers.getsigners function.

[0:52] It will return an array of addresses where the first item will be the owner. Then, retrieve the balance of these addresses using the GetBalance function from the address and trigger the transaction.

[1:08] You have the contract instance already defined above. You'll need to copy this piece of code, but instead of copying it, let's move this logic to its own block inside and before all function. Now, you can access the contract from any part of the test.

[1:27] Time to perform a transaction by connecting the sender address with the contract. This address is the one that will interact with the contract and perform the syntax function by adding as arguments the message, the name, and the amount you want to send.

[1:46] Let's fix this type here. Now, to define the value, you cannot directly pass any number. You need to parse it as ether using the ethers. YouTube.parse ethers function. Let's use .001ethers. Then, wait for the transaction to be done and retrieve the new balances by using the owner.getbalance, again, same for the sender.

[2:20] Let's compare them using some assertions. Check that the new balance is above the previous one. Do the same with the sender balance, but in this case, check that the balance is below the previous value. Also, let's check the contract total tips variable.

[2:43] You can also test these changes by using a custom matcher provided by the Waffle framework. This is the changeEthersBalance function that assert that the transaction changes the balance of a particular address in a certain amount.

[3:03] Now, run the test. Open your terminal, be sure to be in the project folder, and run MPM run hardhat test, and wait for the results. All the tests are passing, but the contract has another function. Let's write a test for it, should return all the tips.

[3:26] Again, an asynchronous function, define the amount you want to use for this transaction, retrieve the sender address using the getSigners function. You can copy the previous transaction code but by sending a different amount with a different message and name. Send the transaction and wait for the result.

[3:50] Now, retrieve the tips by using the getAllTips function from the contract. Check that the Total Tips, public variable, is equal to two, because there are two transactions.

[4:03] Also, check that the array size is equal to two. At last, check the content of the tips array by comparing the values of each item. Let's run the test again. There you have three tests passing, but there is one more scenario. Check for a failure transaction. The contract code says that a transaction will fail, if the amount that the user wants to send is bigger than its own balance.

[4:35] Let's write this scenario. Retrieve the sender address using, again, the getSigners function. Set an insane amount of Ether, and try to perform the transaction. Copy and paste the same transaction code used before, but let's remove the waiting call of it because it's not needed.

[4:56] Now, write the assertion waiting for the transaction to be reversed. Run the test. You have four tests passing, but the contract is throwing a message when the transaction fails. You can assert that or test for that scenario by using the reverted with args function, and passing the text of the message to it. Run the test again, and all the scenarios were covered.