Vai al contenuto principale

Come simulare (mock) i contratti intelligenti in Solidity per i test

Solidity
contratti intelligenti
test
mocking
Intermedio
Markus Waas
2 maggio 2020
4 minuti di lettura

Gli oggetti mock (opens in a new tab) (o simulati) sono un design pattern comune nella programmazione orientata agli oggetti. Derivante dall'antica parola francese 'mocquer' con il significato di 'prendere in giro', si è evoluto in 'imitare qualcosa di reale', che è in realtà ciò che facciamo nella programmazione. Per favore, prendi in giro i tuoi contratti intelligenti solo se lo desideri, ma simulali (mock) ogni volta che puoi. Ti semplificherà la vita.

Test unitari dei contratti con i mock

Simulare (mocking) un contratto significa essenzialmente creare una seconda versione di quel contratto che si comporta in modo molto simile all'originale, ma in un modo che può essere facilmente controllato dallo sviluppatore. Spesso ci si ritrova con contratti complessi in cui si desidera solo eseguire test unitari su piccole parti del contratto. Il problema è: cosa succede se testare questa piccola parte richiede uno stato del contratto molto specifico e difficile da raggiungere?

Potresti scrivere ogni volta una complessa logica di configurazione dei test che porti il contratto nello stato richiesto, oppure scrivere un mock. Simulare un contratto è facile con l'ereditarietà. Crea semplicemente un secondo contratto mock che eredita da quello originale. Ora puoi sovrascrivere (override) le funzioni nel tuo mock. Vediamolo con un esempio.

Esempio: ERC-20 privato

Usiamo un contratto ERC-20 di esempio che ha un periodo privato iniziale. Il proprietario può gestire gli utenti privati e solo a loro sarà permesso ricevere token all'inizio. Una volta trascorso un certo periodo di tempo, a tutti sarà permesso utilizzare i token. Se sei curioso, stiamo usando l'hook _beforeTokenTransfer (opens in a new tab) dai nuovi contratti OpenZeppelin 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}
Mostra tutto

E ora simuliamolo (mock).

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}
Mostra tutto

Otterrai uno dei seguenti messaggi di errore:

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

Poiché stiamo utilizzando la nuova versione 0.6 di Solidity, dobbiamo aggiungere la parola chiave virtual per le funzioni che possono essere sovrascritte e override per la funzione che sovrascrive. Quindi aggiungiamole a entrambe le funzioni isPublic.

Ora nei tuoi test unitari, puoi usare invece PrivateERC20Mock. Quando vuoi testare il comportamento durante il periodo di utilizzo privato, usa setIsPublic(false) e allo stesso modo setIsPublic(true) per testare il periodo di utilizzo pubblico. Naturalmente nel nostro esempio, potremmo semplicemente usare gli aiutanti temporali (time helpers) (opens in a new tab) per modificare i tempi di conseguenza. Ma l'idea del mocking dovrebbe essere chiara ora e puoi immaginare scenari in cui non è così facile come far semplicemente avanzare il tempo.

Simulare molti contratti

Può diventare caotico se devi creare un altro contratto per ogni singolo mock. Se questo ti infastidisce, puoi dare un'occhiata alla libreria MockContract (opens in a new tab). Ti consente di sovrascrivere e modificare i comportamenti dei contratti al volo. Tuttavia, funziona solo per simulare le chiamate a un altro contratto, quindi non funzionerebbe per il nostro esempio.

Il mocking può essere ancora più potente

I poteri del mocking non finiscono qui.

  • Aggiungere funzioni: non solo è utile sovrascrivere una funzione specifica, ma anche semplicemente aggiungere funzioni aggiuntive. Un buon esempio per i token è avere semplicemente una funzione mint (coniare) aggiuntiva per consentire a qualsiasi utente di ottenere nuovi token gratuitamente.
  • Utilizzo nelle reti di test: quando distribuisci e testi i tuoi contratti sulle reti di test insieme alla tua dApp, prendi in considerazione l'utilizzo di una versione simulata (mock). Evita di sovrascrivere le funzioni a meno che tu non debba davvero farlo. Dopotutto, vuoi testare la logica reale. Ma aggiungere, ad esempio, una funzione di ripristino può essere utile per riportare semplicemente lo stato del contratto all'inizio, senza richiedere una nuova distribuzione. Ovviamente non vorresti avere una cosa del genere in un contratto sulla rete principale.

Ultimo aggiornamento della pagina: 25 agosto 2025

Questo tutorial è stato utile?