Вместе с тем, что в своих тестах мы проверяем полученные значение к ожидаемым, нам требуется также проверять и правильную отработку ошибок и событий. Давайте поговорим, как это происходит.
Для начала добавим новую функцию в наш контракт Counter.sol:
function setNumber100(uint256 newNumber) public {
require(newNumber == 100, 'Wrong numer!');
number = newNumber;
}
Она просто проверяет, чтобы значение для установки в переменную было обязательно равно 100, иначе происходит откат транзакции.Как это протестировать?
Если мы просто хотим убедиться, что данная функция не сработает, если число будет отличное от 100, то можно сделать так:
function testFail_setNumber100() public {
counter.setNumber100(150);
}
Обратите внимание на testFail. Так мы говорим Foundry, что ожидаем откат транзакции. Другими словами, если сейчас транзакция не пройдет, то результатом будет fail и таким образом тест удастся. Поняли, в чем дело?Когда мы пишем testFail, необходимо чтобы транзакция упала - тогда тест пройдет. Если же транзакция завершится нормально, то тест будет "завален". Тут, скажем, немного обратная от привычной логика.
Чуть позже мы увидим более практическое ее применение, а сейчас поговорим, как отслеживать вызов ошибок при тестах.
Как протестировать require и условия с кастомными ошибками?
В Foundry есть так называемые читкоды, которые сильно облегчаю работу с тестами и взаимодействие с локальным блокчейном. Вместе с библиотекой Test мы можем использовать огромное количество читов.
Одним из них является:
vm.expectRevert();Тут мы как бы обращаемся к vm (virtual machine) и говорим, что ожидаем реверт с некоторым сообщением. Наш тест может выглядеть так:
function test_setNumber_Revert() public {
vm.expectRevert("Wrong numer!");
counter.setNumber100(150);
}
Обратите внимание, что читкод мы пишем до исполнения функции, а не после нее. Это очень важно для хода выполнения тестов! Более того, в этом случае мы пишем название теста просто test_testName, а не testFail_testName, т.е. слово fail тут уже не нужно. Далее посмотрим на два вида кастомных ошибок.
Для начала добавим их и две новые функции в контракт:
error WrongNum(address caller, uint256 num);Тесты для обеих функций могут выглядеть так:
error WrongSet();
function setNumber150(uint256 newNumber) public {
if (newNumber != 150) revert WrongSet();
number = newNumber;
}
function setNumber200(uint256 newNumber) public {
if (newNumber != 200) revert WrongNum(msg.sender, newNumber);
number = newNumber;
}
function test_setNumber_Revert150() public {
vm.expectRevert(abi.encodeWithSignature('WrongSet()'));
counter.setNumber150(200);
}
function test_setNumber_Revert200() public {
vm.expectRevert(abi.encodeWithSignature("WrongNum(address,uint256)", address(this),150));
counter.setNumber200(150);
}
Ошибки и их аргументы мы оборачиваем в abi.encodeWithSignature() и это становится нашим ожидаемым результатом тестов.Я видел в нескольких видео, а также в официальной документации, что порой для кастомных ошибок можно использовать более простые конструкции, типа:
function test_setNumber_Revert150() public {
vm.expectRevert('WrongSet.selector);
counter.setNumber150(200);
}
Оба варианта рабочие. Можете смело использовать их в своих тестах.Еще интереснее дела обстоят с порождением событий и их тестированием.
Объявим его в нашем контракте Counter:
event NewEvent(uint256 num);и добавим запись в функцию increment() в виде:
emit NewEvent(number);Наш тест для событий может выглядеть так:
function test_IncrementEvent() public {
vm.expectEmit(true, true, true, false);
emit NewEvent(counter.number());
counter.increment();
}
Сначала мы используем новый читкод vm.expectEmit(), который принимает четыре аргумента в качестве булевых значение. Первые три устанавливаются для отслеживания indexed параметров в событии, последний - нужна ли проверка входных значений. Далее нам нужно породить это событие в нашем тесте и уже после вызвать функцию.
Немного странно, но так это работает.