Why So Many Tests
When I built my NFT marketplace I did not plan a number of tests. Every time I thought "what if someone calls this with..." I added another one. The suite ended up at 62 passing tests: 27 for the NFT contract and 35 for the marketplace contract.
The Mindset
A deployed contract cannot be patched the way a web app can, and it holds other people's money. That changes what a test is for. A test of the happy path shows the function works. The tests that taught me something were the ones that tried to make a function misbehave.
Lessons
Test who is not allowed to call a function
For every owner-only function, there is a test that a regular account gets a revert. That covers minting, setting royalties, updating the platform fee and withdrawing fees. It is easy to write an onlyOwner modifier and forget to check it, so I check it.
Test the edges of every limit
The NFT contract allows batch minting of up to 20 tokens, so there is a test at exactly 20 and a test at 21. Royalties are capped at 10%, so a royalty above that must revert. The platform fee has the same cap. Limits are where the off-by-one mistakes live.
Test the money, not only the state
For a purchase I checked where the money went: the seller, the creator's royalty and the platform fee each had to receive the right amount, and an overpayment had to be refunded. A marketplace can update ownership correctly and still pay the wrong people.
Test the order of operations
Some bugs only appear in sequences. Listing a token that is already listed must revert. Cancelling and then listing again must work. Buying a token that was never listed must revert. A test that runs a full trading cycle, from mint to list to buy, catches the cases the individual tests miss.
What I Used
- Hardhat as the development environment and test runner
- Mocha and Chai for the assertions
- OpenZeppelin for the ERC-721 base, royalties and the marketplace's ReentrancyGuard
Takeaway
Writing the tests taught me more Solidity than any tutorial, because I was trying to break my own code. Coverage numbers matter less than whether each limit and each payment has a test that would fail if someone changed it.
The test suite is on GitHub.