Можно ли написать тест с использованием трюфеля, который пытается подтвердить, что в контракте происходит бросок? Например, если бы у меня был контракт с функцией...
contract TestContract {
function testThrow() {
throw;
}
}
если я пишу тест в трюфеле, который вызывает эту функцию, то тест трюфеля в основном падает с:
Error: VM Exception while executing transaction: invalid JUMP
Есть ли способ обработать это исключение из вашего теста, чтобы убедиться, что бросок действительно произошел? Причина, по которой я хочу это сделать, состоит в том, чтобы проверить, действительно ли мои функции вызывают ошибки, когда пользователь вводит неверный ввод?
Вы можете использовать помощник OpenZeppelin expectThrow -
Источник: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
export default async promise => {
try {
await promise;
} catch (error) {
// TODO: Check jump destination to destinguish between a throw
// and an actual invalid jump.
const invalidJump = error.message.search('invalid JUMP') >= 0;
// TODO: When we contract A calls contract B, and B throws, instead
// of an 'invalid jump', we get an 'out of gas' error. How do
// we distinguish this from an actual out of gas event? (The
// testrpc log actually show an 'invalid jump' event.)
const outOfGas = error.message.search('out of gas') >= 0;
assert(
invalidJump || outOfGas,
"Expected throw, got '" + error + "' instead",
);
return;
}
assert.fail('Expected throw not received');
};
Я использую его в своих тестовых примерах следующим образом:
import expectThrow from './helpers/expectThrow';
.
.
.
describe('borrowBook', function() {
it("should not allow borrowing book if value send is less than 100", async function() {
await lms.addBook('a', 'b', 'c', 'e', 'f', 'g');
await lms.addMember('Michael Scofield', accounts[2], "Ms@gmail.com");
await lms.borrowBook(1, {from: accounts[2], value: 10**12})
await expectThrow(lms.borrowBook(1, {from: accounts[2], value: 10000})); // should throw exception
});
});
На мой взгляд, самый чистый из возможных способов заключается в следующем:
it("should reject", async function () {
try {
await deployedInstance.myOperation1();
assert.fail("The transaction should have thrown an error");
}
catch (err) {
assert.include(err.message, "revert", "The error message should contain 'revert'");
}
});
Нет необходимости в return
. В одной и той же функции может быть выполнено несколько проверок.
Другие ответы в этой теме кажутся действительными, однако я считаю, что этот код более лаконичный и читабельный.
Это работает сsolidity 0.4.12-develop
it("should throw if the car is not blue", function() {
return CarFactory.deployed()
.then(function(factory) {
return factory.createCar("red");
})
.then(assert.fail)
.catch(function(error) {
assert.include(
error.message,
'out of gas',
'red cars should throw an out of gas exception.'
)
});
});
Я заметил, что при использовании truffle+testrpc некоторые throws
вызывают исключение «нет газа», а другие — исключение «неверный код операции». Я не подтвердил причины этих разных сообщений, но они кажутся последовательными. Я советую не проводить наивное тестирование обоих исключений, так как это потенциально полезная информация, если сообщение об исключении изменится.
Вот шаблон, который я сейчас использую для проверки ожидаемых бросков (например, при неверном вводе). Solidity реализует бросок с помощью JUMP в недопустимое место назначения, поэтому мы перехватываем ошибку, а затем ищем строку «invalid JUMP» в сообщении об ошибке... Я бы предпочел более надежный способ, но пока ничего другого не нашел.
var EthWall = artifacts.require("./EthWall.sol");
contract('TestContract', function(accounts) {
it("should throw an exception", function() {
return EthWall.deployed().then(function(instance) {
return instance.testThrow.call();
}).then(function(returnValue) {
assert(false, "testThrow was supposed to throw but didn't.");
}).catch(function(error) {
if(error.toString().indexOf("invalid JUMP") != -1) {
console.log("We were expecting a Solidity throw (aka an invalid JUMP), we got one. Test succeeded.");
} else {
// if the error is something else (e.g., the assert from previous promise), then we fail the test
assert(false, error.toString());
}
});
});
});
.then()
блок на .then(assert.fail)
. Я опубликовал еще один ответ с полным кодом в этом вопросе.Вы можете использовать этот gist, который я создал :
var expectExceptionPromise = функция (действие, gasToUse) { вернуть новое обещание (функция (разрешить, отклонить) { пытаться { решить (действие ()); } поймать(е) { отклонить (е); } }) .затем (функция (txn) { // https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6 вернуть web3.eth.getTransactionReceiptMined(txn); }) .then(функция (квитанция) { // Мы в Гете assert.equal(receipt.gasUsed, gasToUse, "следует использовать весь газ"); }) .поймать (функция (е) { if ((e + "").indexOf("недопустимый ПРЫЖОК") || (e + "").indexOf("кончился газ") > -1) { // Мы в TestRPC } else if ((e + "").indexOf("пожалуйста, проверьте количество газа") > -1) { // Мы находимся в Geth для развертывания } еще { бросить е; } }); };
it("", function() {})
тесты ни на TestRPC, ни на Geth. Возможно, оба ваших теста используют общую переменную.Другие ответы не будут работать для более новых версий Solidity ( 0.4.10
и выше, я думаю).
Вместо этого я использую аналогичный шаблон, но с двумя сравнениями строк, чтобы поймать новое сообщение об ошибке, а также старое (только для устаревших контрактов/тестов).
function assertThrows (fn, args) {
//Asserts that `fn(args)` will throw a specific type of error.
return new Promise(
function(resolve, reject){
fn.apply(this, args)
.then(() => {
assert(false, 'No error thrown.');
resolve();
},
(error) => {
var errstr = error.toString();
var newErrMsg = errstr.indexOf('invalid opcode') != -1;
var oldErrMsg = errstr.indexOf('invalid JUMP') != -1;
if(!newErrMsg && !oldErrMsg)
assert(false, 'Did not receive expected error message');
resolve();
})
})
}
Начиная с версии 2.0 в OpenZeppelin вместо expectThrow
. Вот как это использовать:
import {reverting} from 'openzeppelin-solidity/test/helpers/shouldFail';
it('your test name', async () => {
await reverting(contract.myMethod(argument1, argument2, {from: myAccount}));
})
Большинство ответов на этот вопрос, в которых используются встроенные операторы try catch, добавляют довольно много шаблонов ко всем тестам, которые пытаются использовать этот метод. Вместо этого моя truffle-assertions
библиотека позволяет вам делать утверждения для любого вида броска Solidity или сбоя функции очень простым способом.
Библиотеку можно установить через npm и импортировать в начало тестового файла javascript:
npm install truffle-assertions
const truffleAssert = require('truffle-assertions');
После чего его можно использовать внутри тестов:
await truffleAssert.fails(contract.failingFunction(), truffleAssert.ErrorType.INVALID_JUMP);
В OpenZeppelin есть expectThrow
помощник, который может помочь в этом. Это находится вtest/helpers/expectThrow.js
module.exports = async promise => {
try {
await promise;
} catch (error) {
// TODO: Check jump destination to destinguish between a throw
// and an actual invalid jump.
const invalidOpcode = error.message.search('invalid opcode') >= 0;
// TODO: When we contract A calls contract B, and B throws, instead
// of an 'invalid jump', we get an 'out of gas' error. How do
// we distinguish this from an actual out of gas event? (The
// testrpc log actually show an 'invalid jump' event.)
const outOfGas = error.message.search('out of gas') >= 0;
assert(
invalidOpcode || outOfGas,
"Expected throw, got '" + error + "' instead",
);
return;
}
assert.fail('Expected throw not received');
};
пример использования, test/MintableToken.js
например:
import expectThrow from './helpers/expectThrow';
...
await expectThrow(token.mint(accounts[0], 100));
...
Вот еще один подход (вдохновленный приведенными выше решениями).
При определении пользовательских функций ожидания, подобных этим (не стесняйтесь добавлять больше), я чувствую, что тесты более четко показывают, что вы ожидаете.
// expectThrow.js
const expectThrow = (text) => async (promise) => {
try {
await promise;
} catch (error) {
assert(error.message.search(text) >= 0, "Expected throw, got '" + error + "' instead")
return
}
assert.fail('Expected throw not received')
}
module.exports = {
expectOutOfGas: expectThrow('out of gas'),
expectRevert: expectThrow('revert'),
expectInvalidJump: expectThrow('invalid JUMP')
}
Затем в вашем тесте вы делаете, например:
/// test.js
const { expectRevert } from './expectThrow.js'
it('your test name', async () => {
await expectRevert(
// your contract call
)
})
рстормсф
Санчит
Санчит
Марс Робертсон
SyntaxError: Unexpected token import
github.com/trufflesuite/truffle/issues/664Бен Бернс
truffle-contract
. Если это не так, пожалуйста, поднимите вопрос !Утгарда
shouldFail
, подробности см. В моем ответе.