Solidity test code is put in .sol files. Here are the things you need to note about Solidity tests before writing tests using Solidity:
- Solidity tests shouldn't extend from any contract. This makes your tests as minimal as possible and gives you complete control over the contracts you write.
- Truffle provides a default assertion library for you, but you can change this library at any time to fit your needs.
- You should be able to run your Solidity tests against any ethereum client.
To learn how to write tests in Solidity, let's explore the default Solidity test code generated by truffle. This is the code, and it can be found in the TestMetacoin.sol file:
pragma Solidity ^0.4.2;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
function testInitialBalanceWithNewMetaCoin() {
MetaCoin meta = new MetaCoin();
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
}
Here is how the preceding code works:
- Assertion functions such as Assert.equal() are provided to you by the truffle/Assert.sol library. This is the default assertion library; however, you can include your own assertion library as long as the library loosely integrates with truffle's test runner by triggering the correct assertion events. Assertion functions fire events, which are caught by truffle, and the information is displayed. This is the architecture of Solidity assertion libraries in truffle. You can find all the available assertion functions in Assert.sol (https://github.com/ConsenSys/truffle/blob/beta/lib/testing/Assert.sol).
- In the import path, truffle/Assert.sol, truffle is the package name. We will learn more about packages later.
- The addresses of your deployed contracts (that is, contracts that were deployed as part of your migrations) are available through the truffle/DeployedAddresses.sol library. This is provided by truffle and is recompiled and relinked before each test suite is run. This library provides functions for all of your deployed contracts in the form of DeployedAddresses.<contract name>(). This will return an address that you can then use to access that contract.
- In order to use the deployed contract, you'll have to import the contract code into your test suite. Notice import "../contracts/MetaCoin.sol"; in the preceding example. This import is relative to the test contract, which exists in the ./test directory, and it goes outside of the test directory in order to find the MetaCoin contract. It then uses that contract to cast the address to the MetaCoin type.
- All test contracts must start with Test, using an uppercase T. This distinguishes this contract from test helpers and project contracts (that is, the contracts under test), letting the test runner know which contracts represent test suites.
- Like test contract names, all test functions must start with test, in lowercase. Each test function is executed as a single transaction in order of appearance in the test file (such as your JavaScript tests). Assertion functions provided by truffle/Assert.sol trigger events that the test runner evaluates to determine the result of the test. Assertion functions return a Boolean that represents the outcome of the assertion, which you can use to return from the test early to prevent execution errors (that is, errors that testrpc will expose).
- You are provided with many test hooks, shown in the following example. These hooks are beforeAll, beforeEach, afterAll, and afterEach, which are the same hooks provided by mocha in your JavaScript tests. You can use these hooks to perform setup and teardown actions before and after each test or before and after each suite is run. Like test functions, each hook is executed as a single transaction. Note that some complex tests will need to perform a significant amount of setup that might overflow the gas limit of a single transaction; you can get around this limitation by creating many hooks with different suffixes, as shown in the following example:
import "truffle/Assert.sol";
contract TestHooks {
uint someValue;
function beforeEach() {
someValue = 5;
}
function beforeEachAgain() {
someValue += 1;
}
function testSomeValueIsSix() {
uint expected = 6;
Assert.equal(someValue, expected, "someValue should have been 6");
}
}
- This test contract also shows that your test functions and hook functions all share the same contract state. You can set up the contract data before the test, use that data during the test, and reset it afterward in preparation for the next one. Note that just like your JavaScript tests, your next test function will continue from the state of the previous test function that ran.
Truffle doesn't provide a direct way to test whether your contract should and shouldn't throw exception (that is, for contracts that use throw to signify an expected error). But a hacky solution is there for this, which you can find at http://truffleframework.com/tutorials/testing-for-throws-in-Solidity-tests.