npm package: @mangrovedao/hardhat-test-solidity
Description: Hardhat plugin for writing tests in solidity
Author: Giry
Latest version: 0.0.17

# hardhat-test-solidity

💡 Inspired by [dapptools](https://github.com/dapphub/dapptools)'s [ds-test](https://github.com/dapphub/ds-test).

⚠️ Requires [hardhat-deploy](https://github.com/wighawag/hardhat-deploy) as a peer dependency.

## tl;dr

Tests suites are solidity contracts, tests are solidity functions.

## Install
> terminal
```
$ npm install @mangrovedao/hardhat-test-solidity
```

> hardhat.config.js
```javascript
require('@mangrovedao/hardhat-test-solidity');
```

> MyContract_Test.sol:
```solidity
// Adds Test library to the context
import Test from "@mangrovedao/hardhat-test-solidity/test.sol";

// `_Test` suffix means it is a test contract
contract MyContract_Test {

  receive() external payable {} // necessary to receive eth from test runner

  // `_test` suffix means it is a test function
  function addition_test() public {
    prepare();
    // Logging will be interpreted by hardhat-test-solidity
    Test.eq(4,2+2,"oh no");
  }

  // Will not be interpreted as a test function
  function prepare() public {}
}
```

> terminal
```
$ npx hardhat test-solidity MyContract
```

## How to structure test contract
* All contracts with suffix `_Test` are test contracts.
* All public functions with suffix `_test` are test functions.
* All public functions with suffix `_beforeAll` setup the state before other tests.
* If you have the functions `fn_test` and `fn_before`, `fn_before` will run, then `fn_test`, without state revert.

### Tips
* You must make a `receive` function so your test contracts get ethers.
* State is reverted between tests
* Functions are sorted alphabetically before they are added to test suite
* You probably want a `MyContract_Test` for every `MyContract`.
* Having multiple `_beforeAll` functions rather than setting things up in the constructor means you can split setup into multiple transactions to go around gas limits.

## How to test for stuff
* `Test.check(bool success,string memory message)` succeeds if `success` is true.
* `Test.eq(actual,expected,message)` for testing `bytes32`, `bool`, `string`, `uint`, `address` equality.
* `Test.eq0(actual,expected,message)` for testing `bytes` equality.
* `Test.less(uint a, uint b,message)` succeeds if `a < b`.
* `Test.more(uint a, uint b,message)` succeeds if `a > b`.
* `Test.fail(message)` to always fail.
* `Test.succeed()` to always succeed.

## How to test for event emission
Suppose you want to make sure that contract `Market` emits the `Trade` event.
```
import "@mangrovedao/hardhat-test-solidity/test.sol";
contract My_Test {
  receive() external payable {} // necessary to receive eth from test runner

  Market amm;

  function _beforeAll() {
    amm = new Market()
  }

  function first_test() {
    Market.trade();

    Test.expectFrom(amm);
    emit Market.Trade();
  }
}
```

So: you _emit_ an event (with any arguments you like), and the plugin will check that `amm` has already emitted the exact same event.


### Tips
* For a given address, the order of events matters. Between addresses it does not.
* Events are checked _after their emission_. So do all your test, and at the end test for events. 
* If you want to normally emit events from a test after you already called `Test.expectFrom(address)`, call `Test.stopExpecting()`;


## How to use the command line
`npx hardhat test-solidity [contract names without _Test] [--prefix function_prefix]`

Add `--details` for more detailed logging, including logs generated by the `logFormatters` plugin option (see below).

For more CLI options look at
> `npx hardhat test-solidity --help`

### Tip
If you want to only run the test `breath_is_fire_test` in the testing contract `Dragon_Test`, and you have 10 testing contracts, run 

```test-solidity Dragon --prefix breath_is_fire``` 

rather than just 

```test-solidity --prefix breath_is_fire``` 

so you don't waste time deploying all the other test suite contracts.

## How to log
To get nicely-formatted logs, use the `Display` library. There are
* `Display.log(uint|string)`
* `Display.log(uint|string,uint|string)`
* `Display.log(uint|string,uint|string,uint|string)`
* `Display.logBalances(address[1] memory tokens, address a0)`
* `Display.logBalances(address[1] memory tokens, address a0, address a1)`
* `Display.logBalances(address[1] memory tokens, address a0, address a1, address a2)`
* `Display.logBalances(address[2] memory tokens, address a0)`
* `Display.logBalances(address[2] memory tokens, address a0, address a1)`
* `Display.logBalances(address[2] memory tokens, address a0, address a1, address a2)`

## How to register addresses
To get pretty-printing of addresses, in your test setup do

```solidity
import {Display as D} from "@mangrovedao/hardhat-test-solidity/test.sol";
...
D.register(address addr, string memory name)
```
then when using `--show-events` and wherever addresses are used, `name` will be shown instead of `addr`. 

## How to configure the plugin
> `hardhat.config.js`
```javascript
{
  ...,
  testSolidity: { // default values as follows:
    timeout: 300_000 /* test suite timeout in ms */,
    logFormatters: (hre,formatArg) => { return {}; } /* format logs */
    testers: (hre,formatArg,assert) => { return {}; } /* format logs */
  }
}
```

### Custom log formatters
`logFormatters(hre,formatArg):object` is a function that takes the `hre` hardhat runtime environment and a `formatArg(arg,type?):string` utility function. `arg` is dynamically tested and `type` is an optional type hint (it can be `uint`, `address`, or an array of type hints) to help formatting.

`logFormatters` should return an object where keys are event names and values are formatting functions that should directly log to console and have type:

```typescript
(log:ethers.LogDescription,rawLog:{topics,data},originator:string):void
```

#### Tip
See `src/logFormatters.js` for examples.

### Custom testers/assertions
`testers(hre,formatArg,assert/*assert library*/)` takes the `hre`, `formatArg`, and the [chai](https://www.chaijs.com/) assert object, and returns an object where keys are test event names and values are of the form :

```javascript
{
  trigger({success,message,actual,expected}) : void
}
```

You should create the corresponding events in your tests so that they can be interpreted by your functions.

#### Tip
See `src/testers.js` for examples.

## Debugging

This plugin uses the [debug](https://www.npmjs.com/package/debug) package. To debug this plugin only do:

> `DEBUG='hardhat:test-solidity:*' npx hardhat test-solidity [args]`