Dapple provides a VM test harness so you can write your tests directly in Solidity. This is less flexible and sometimes more verbose than writing tests in the harness language, but the lack of a context switch makes writing unit tests more pleasant for the developer.
Tests
Simple Example
Advanced Example
Suppose you want to test this contract:
contract MyRegistry {
address public _creator;
mapping(bytes32=>bytes32) _values;
event Set(bytes32 indexed key, bytes32 value);
function MyRegistry() {
_creator = msg.sender;
}
function set(bytes32 key, bytes32 value) {
if( msg.sender != _creator ) {
throw;
}
_values[key] = value;
Set(key, value);
}
function get(bytes32 key) constant returns (bytes32 value) {
return _values[key];
}
}
A dapple test might look like this:
import 'dapple/test.sol'; // virtual "dapple" package imported when `dapple test` is run
import 'myregistry.sol';
// Deriving from `Test` marks the contract as a test and gives you access to various test helpers.
contract MyRegistryTest is Test {
MyRegistry reg;
Tester proxy_tester;
// The function called "setUp" with no arguments is
// called on a fresh instance of this contract before
// each test. TODO: Document when to put setup logic in
// setUp vs subclass constructor when writing Test subclasses
function setUp() {
reg = new MyRegistry();
proxy_tester = new Tester();
proxy_tester._target(reg);
}
function testCreatorIsCreator() {
assertEq( address(this), reg._creator() );
}
function testFailNonCreatorSet() {
MyRegistry(proxy_tester).set("no", "stop");
}
event Set(bytes32 indexed key, bytes32 value);
function testSetEvent() {
expectEventsExact(reg);
Set("hello", "hi");
reg.set("hello", "hi");
}
}
Test Exeptions
This test feature captures thrown Errors (VM exceptions) of a transaction.
All test functions which are starting with testThrow
, testFail
or testError
are expected to crash: a throw;
is expected somewhere in the scenario.
Example
Suppose the following contract:
contract Contract {
[...]
function crash() {
throw;
}
function passing() {
// nothing
}
}
passing
The following shows a passing test, because an expected throw actually happens:
contract MyTest is Test {
function testThrow() {
Contract target = new Contract();
target.crash();
}
}
failing
The following test fails, because the function name has a wrong prefix:
contract MyTest is Test {
function testCrash() {
Contract target = new Contract();
target.crash();
}
}
The following test fails, because no expected throw happens:
contract MyTest is Test {
function testError() {
Contract target = new Contract();
target.passing();
}
}
Test Events
This test feature tests the exact emitted event sequence produced by a transaction.
Example
A contract which implements a set of Events:
contract EventDefinitions {
event info(bytes data);
event warn(bytes data);
}
contract Contract is EventDefinitions {
[...]
function fire() {
info("ok");
warn("warning");
}
}
In order to assert that in a scenario a correct sequence of events is emitted
one can bind the events of contract instance with expectEventsExact( <target> )
.
After a binding, the test function has to emit the expected events in the same
order in which they are expect in the bound instance.
This assert the correct event types, correct inputs for a type and the
correct order of emits. Also expected but not emitted and unexpected
events are leading to a test fail.
The easiest way to use this is to follow the pattern of defining events in their own
container type like EventDefinitions
, then have both the implementation and the tester
derive from it.
passing example
The following shows a passing test:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
info("ok");
warn("warning");
target.fire();
}
}
failing examples
The following test will fail because of the wrong order of the events:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
warn("warning");
info("ok");
target.fire();
}
}
The following test will fail because of the wrong type of the events:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
info("ok");
info("warning");
target.fire();
}
}
The following test will fail because of the wrong content of the events:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
info("ok");
warn("error");
target.fire();
}
}
The following test will fail because an unexpected event is emited:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
info("ok");
target.fire();
}
}
The following test will fail because an expected event is not emited:
contract MyTest is Test, EventDefinitions {
[...]
function testEvents () {
Contract target = new Contract();
expectEventsExact( target );
info("ok");
warn("warn");
info("success");
target.fire();
}
}