Wie man Solidity-Smart-Contracts für Tests mockt
Mock-Objekte (opens in a new tab) sind ein gängiges Entwurfsmuster in der objektorientierten Programmierung. Abgeleitet vom altfranzösischen Wort „mocquer“ mit der Bedeutung „sich über etwas lustig machen“, entwickelte es sich zu „etwas Reales imitieren“, was genau das ist, was wir in der Programmierung tun. Bitte machen Sie sich nur über Ihre Smart Contracts lustig, wenn Sie das möchten, aber mocken Sie sie, wann immer Sie können. Es macht Ihr Leben einfacher.
Unit-Tests von Contracts mit Mocks
Einen Contract zu mocken bedeutet im Wesentlichen, eine zweite Version dieses Contracts zu erstellen, die sich sehr ähnlich wie das Original verhält, aber auf eine Weise, die vom Entwickler leicht kontrolliert werden kann. Oft hat man es mit komplexen Contracts zu tun, bei denen man nur kleine Teile des Contracts mit Unit-Tests prüfen möchte. Das Problem ist: Was ist, wenn das Testen dieses kleinen Teils einen sehr spezifischen Contract-Zustand erfordert, der schwer zu erreichen ist?
Sie könnten jedes Mal eine komplexe Test-Setup-Logik schreiben, die den Contract in den erforderlichen Zustand versetzt, oder Sie schreiben einen Mock. Das Mocken eines Contracts ist durch Vererbung einfach. Erstellen Sie einfach einen zweiten Mock-Contract, der vom ursprünglichen erbt. Nun können Sie Funktionen für Ihren Mock überschreiben. Sehen wir uns das an einem Beispiel an.
Beispiel: Privater ERC-20
Wir verwenden einen beispielhaften ERC-20-Contract, der eine anfängliche private Zeitspanne hat. Der Eigentümer kann private Benutzer verwalten und nur diese dürfen zu Beginn Token erhalten. Sobald eine bestimmte Zeit verstrichen ist, darf jeder die Token verwenden. Falls Sie neugierig sind: Wir verwenden den Hook _beforeTokenTransfer (opens in a new tab) aus den neuen 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}Und nun lassen Sie uns diesen mocken.
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}Sie erhalten eine der folgenden Fehlermeldungen:
PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.
Da wir die neue Solidity-Version 0.6 verwenden, müssen wir das Schlüsselwort virtual für Funktionen hinzufügen, die überschrieben werden können, und override für die überschreibende Funktion. Fügen wir diese also zu beiden isPublic-Funktionen hinzu.
In Ihren Unit-Tests können Sie nun stattdessen PrivateERC20Mock verwenden. Wenn Sie das Verhalten während der privaten Nutzungszeit testen möchten, verwenden Sie setIsPublic(false) und analog setIsPublic(true) zum Testen der öffentlichen Nutzungszeit. Natürlich könnten wir in unserem Beispiel auch einfach Zeit-Hilfsfunktionen (Time Helpers) (opens in a new tab) verwenden, um die Zeiten entsprechend zu ändern. Aber die Idee des Mockings sollte nun klar sein und Sie können sich Szenarien vorstellen, in denen es nicht so einfach ist, wie nur die Zeit vorzudrehen.
Viele Contracts mocken
Es kann unübersichtlich werden, wenn Sie für jeden einzelnen Mock einen weiteren Contract erstellen müssen. Wenn Sie das stört, können Sie sich die Bibliothek MockContract (opens in a new tab) ansehen. Sie ermöglicht es Ihnen, das Verhalten von Contracts im laufenden Betrieb (on-the-fly) zu überschreiben und zu ändern. Sie funktioniert jedoch nur für das Mocken von Aufrufen an einen anderen Contract, würde also für unser Beispiel nicht funktionieren.
Mocking kann noch mächtiger sein
Die Möglichkeiten des Mockings enden hier nicht.
- Funktionen hinzufügen: Nicht nur das Überschreiben einer bestimmten Funktion ist nützlich, sondern auch das einfache Hinzufügen zusätzlicher Funktionen. Ein gutes Beispiel für Token ist eine zusätzliche
mint-Funktion, die es jedem Benutzer ermöglicht, kostenlos neue Token zu erhalten. - Verwendung in Testnets: Wenn Sie Ihre Contracts zusammen mit Ihrer Dapp in Testnets bereitstellen und testen, sollten Sie die Verwendung einer gemockten Version in Betracht ziehen. Vermeiden Sie es, Funktionen zu überschreiben, es sei denn, Sie müssen es wirklich tun. Schließlich wollen Sie die echte Logik testen. Aber das Hinzufügen einer Reset-Funktion kann beispielsweise nützlich sein, um den Contract-Zustand einfach auf den Anfang zurückzusetzen, ohne dass eine neue Bereitstellung erforderlich ist. Offensichtlich würden Sie so etwas nicht in einem Mainnet-Contract haben wollen.
Letzte Aktualisierung der Seite: 3. März 2026