跳至主要内容

協助翻譯本頁面

🌏

由於尚未翻譯,本頁面內容以英文顯示。協助我們翻譯內容。

翻譯本頁面

這裡沒有漏洞!🐛

此頁面未翻譯,目前特意維持英文原文。

How to mock Solidity smart contracts for testing

solidity
smart contracts
testing
mocking
Intermediate
✍Markus Waas
📚soliditydeveloper.com
📆2020年5月2日
⏱️4 minute read

Mock objects are a common design pattern in object-oriented programming. Coming from the old French word 'mocquer' with the meaning of 'making fun of', it evolved to 'imitating something real' which is actually what we are doing in programming. Please only make fun of your smart contracts if you want to, but mock them whenever you can. It makes your life easier.

Unit-testing contracts with mocks

Mocking a contract essentially means creating a second version of that contract which behaves very similar to the original one, but in a way that can be easily controlled by the developer. You often end up with complex contracts where you only want to unit-test small parts of the contract. The problem is what if testing this small part requires a very specific contract state that is difficult to end up in?

You could write complex test setup logic everytime that brings in the contract in the required state or you write a mock. Mocking a contract is easy with inheritance. Simply create a second mock contract that inherits from the original one. Now you can override functions to your mock. Let us see it with an example.

Example: Private ERC20

We use an example ERC-20 contract that has an initial private time. The owner can manage private users and only those will be allowed to receive tokens at the beginning. Once a certain time has passed, everyone will be allowed to use the tokens. If you are curious, we are using the _beforeTokenTransfer hook from the new OpenZeppelin contracts v3.

1pragma solidity ^0.6.0;
2
3import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4import "@openzeppelin/contracts/access/Ownable.sol";
5
6contract PrivateERC20 is ERC20, Ownable {
7 mapping (address => bool) public isPrivateUser;
8 uint256 private publicAfterTime;
9
10 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
11 publicAfterTime = now + privateERC20timeInSec;
12 }
13
14 function addUser(address user) external onlyOwner {
15 isPrivateUser[user] = true;
16 }
17
18 function isPublic() public view returns (bool) {
19 return now >= publicAfterTime;
20 }
21
22 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
23 super._beforeTokenTransfer(from, to, amount);
24
25 require(_validRecipient(to), "PrivateERC20: invalid recipient");
26 }
27
28 function _validRecipient(address to) private view returns (bool) {
29 if (isPublic()) {
30 return true;
31 }
32
33 return isPrivateUser[to];
34 }
35}
36
顯示全部
📋 複製

And now let's mock it.

1pragma solidity ^0.6.0;
2import "../PrivateERC20.sol";
3
4contract PrivateERC20Mock is PrivateERC20 {
5 bool isPublicConfig;
6
7 constructor() public PrivateERC20(0) {}
8
9 function setIsPublic(bool isPublic) external {
10 isPublicConfig = isPublic;
11 }
12
13 function isPublic() public view returns (bool) {
14 return isPublicConfig;
15 }
16}
17
顯示全部
📋 複製

You will get one of the following error messages:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.

Since we are using the new 0.6 Solidity version, we have to add the virtual keyword for functions that can be overridden and override for the overriding function. So let us add those to both isPublic functions.

Now in your unit tests, you can use PrivateERC20Mock instead. When you want to test the behaviour during the private usage time, use setIsPublic(false) and likewise setIsPublic(true) for testing the public usage time. Of course in our example, we could just use time helpers to change the times accordingly as well. But the idea of mocking should be clear now and you can imagine scenarios where it is not as easy as simply advancing the time.

Mocking many contracts

It can become messy if you have to create another contract for every single mock. If this bothers you, you can take a look at the MockContract library. It allows you to override and change behaviours of contracts on-the-fly. However, it works only for mocking calls to another contract, so it would not work for our example.

Mocking can be even more powerful

The powers of mocking do not end there.

  • Adding functions: Not only overriding a specific function is useful, but also just adding additional functions. A good example for tokens is just having an additional mint function to allow any user to get new tokens for free.
  • Usage in testnets: When you deploy and test your contracts on testnets together with your dapp, consider using a mocked version. Avoid overriding functions unless you really have to. You want to test the real logic after all. But adding for example a reset function can be useful that simply resets the contract state to the beginning, no new deployment required. Obviously you would not want to have that in a Mainnet contract.
最後編輯: , Invalid DateTime
編輯頁面

這個頁面對您有幫助嗎?

網站上次更新: 2022年9月26日

使用以太坊

  • 尋找錢包
  • 取得以太幣
  • 去中心化應用程式 (dapp)
  • 第二層
  • 執行節點
  • 穩定幣
  • Stake ETH

生態系統

  • 社群中心
  • 以太坊基金會
  • 以太坊基金會部落格
  • 生態系統支援計畫
  • 以太坊漏洞懸賞計畫
  • 生態系統獎助金計畫
  • 以太坊品牌資產
  • Devcon