¿Es posible escribir una prueba usando trufa que intente confirmar que se produce un lanzamiento en un contrato? Por ejemplo, si tuviera un contrato con función...
contract TestContract {
function testThrow() {
throw;
}
}
si escribo una prueba en truffle que invoque esta función, entonces la prueba de truffle básicamente falla con:
Error: VM Exception while executing transaction: invalid JUMP
¿Hay alguna forma de manejar esta excepción desde su prueba para verificar que realmente ocurrió el lanzamiento? ¿La razón por la que quiero hacerlo es para probar que mis funciones realmente se lanzan cuando el usuario pasa una entrada no válida?
Puede usar el ayudante expectThrow de OpenZeppelin:
Fuente: 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');
};
Lo uso en mis casos de prueba así:
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
});
});
En mi opinión, la forma más limpia posible es la siguiente:
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'");
}
});
No hay necesidad de return
. Se pueden realizar varias comprobaciones en la misma función.
Las otras respuestas en este hilo parecen ser válidas, sin embargo, creo que este código es más breve y legible.
esto funciona consolidity 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.'
)
});
});
He notado que cuando uso truffle+testrpc algunos throws
causan una excepción de 'sin gasolina' y otros una excepción de 'código de operación no válido'. No he confirmado las causas de estos diferentes mensajes, pero parecen ser consistentes. No aconsejo probar ingenuamente ambas excepciones, ya que es información potencialmente útil si cambia el mensaje de excepción.
Aquí está el patrón que uso actualmente para probar los lanzamientos esperados (por ejemplo, en una entrada no válida). Solidity implementa throw by JUMPing a un destino no válido, por lo que detectamos el error y luego buscamos la cadena "JUMP no válido" en el mensaje de error... Prefiero tener una forma más robusta pero no he encontrado nada más todavía.
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()
bloque con .then(assert.fail)
. He publicado otra respuesta con el código completo en esta pregunta.Puedes usar esta esencia que creé :
var ExpectedExceptionPromise = function (acción, gasToUse) { return new Promise(función (resolver, rechazar) { probar { resolver (acción ()); } atrapar (e) { rechazar (e); } }) .entonces(función (txn) { // https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6 volver web3.eth.getTransactionReceiptMined(txn); }) .then(función (recibo) { // Estamos en Geth afirmar.equal(recibo.gasUsed, gasToUse, "debería haber usado todo el gas"); }) .catch(función (e) { if ((e + "").indexOf("JUMP no válido") || (e + "").indexOf("sin gasolina") > -1) { // Estamos en TestRPC } else if ((e + "").indexOf("verifique la cantidad de gasolina") > -1) { // Estamos en Geth para un despliegue } más { tirar e; } }); };
it("", function() {})
pruebas posteriores, ni en TestRPC ni en Geth. Quizás ambas pruebas estén usando una variable común.Las otras respuestas no funcionarán para las versiones más nuevas de Solidity ( 0.4.10
y superiores, creo).
En su lugar, uso un patrón similar, pero con dos comparaciones de cadenas para detectar el nuevo mensaje de error, así como el anterior (solo para contratos/pruebas heredados).
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();
})
})
}
A partir de la versión 2.0, OpenZeppelin tiene el asistente expectEvent en lugar de expectThrow
. Aquí hay una manera de usarlo:
import {reverting} from 'openzeppelin-solidity/test/helpers/shouldFail';
it('your test name', async () => {
await reverting(contract.myMethod(argument1, argument2, {from: myAccount}));
})
La mayoría de las respuestas a esta pregunta que usan declaraciones de captura de prueba en línea agregan un poco de repetitivo a todas las pruebas que intentan usar este método. En cambio, mi truffle-assertions
biblioteca le permite hacer afirmaciones para cualquier tipo de lanzamiento de Solidity o falla de función de una manera muy sencilla.
La biblioteca se puede instalar a través de npm e importar en la parte superior del archivo javascript de prueba:
npm install truffle-assertions
const truffleAssert = require('truffle-assertions');
Después de lo cual se puede utilizar dentro de las pruebas:
await truffleAssert.fails(contract.failingFunction(), truffleAssert.ErrorType.INVALID_JUMP);
OpenZeppelin tiene un expectThrow
ayudante que es útil para esto. Está localizado entest/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');
};
el uso de ejemplo está en, test/MintableToken.js
por ejemplo:
import expectThrow from './helpers/expectThrow';
...
await expectThrow(token.mint(accounts[0], 100));
...
Aquí hay otro enfoque (inspirado en las soluciones anteriores).
Al definir funciones esperadas personalizadas como estas (siéntase libre de agregar más), creo que las pruebas son más explícitas sobre lo que espera.
// 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')
}
Luego, en tu prueba, haces, por ejemplo:
/// test.js
const { expectRevert } from './expectThrow.js'
it('your test name', async () => {
await expectRevert(
// your contract call
)
})
tormentasf
Sanchit
Sanchit
Marte Robertson
SyntaxError: Unexpected token import
github.com/trufflesuite/truffle/issues/664ben quema
truffle-contract
. Si no es así, por favor plantee un problema .Utgarda
shouldFail
, verifique mi respuesta para obtener más detalles.