Unit Tests
Testing solidity libraries with structs
When we are testing a solidity library and there is a function that receives a struct as an argument, a CALL_EXCEPTION error may occur.
For example:
library DynamicMetadata {
struct Attribute {
string displayType;
string traitType;
string value;
}
function toBytes(DynamicMetadata.Attribute calldata attribute) public pure returns (bytes memory) {
...
}
}
In this case, we need to test the toBytes function in Javascript using hardhat:
...
const DynamicMetadata = await ethers.getContractFactory('DynamicMetadata', owner);
const dynamicMetadataLibrary = await DynamicMetadata.deploy();
await dynamicMetadataLibrary.deployed();
const attr = {
displayType: 'a',
traitType: 'b',
value: 'c',
};
const attributesInBytes = await dynamicMetadataLibrary.toBytes(attr);
expect(attributesInBytes).to.equal(EXPECTED_VALUE_IN_BYTES);
...
But for some reason (maybe a bug), it throws a CALL_EXCEPTION error.
Apparently, this only occurs with functions with structs parameters in libraries.
A workaround solution is to create a test contract that imports the library and "overrides" the functions that we want to test:
import '../libraries/DynamicMetadata.sol';
contract DynamicMetadataTestContract {
function toBytes(DynamicMetadata.Attribute calldata attribute) public pure returns (bytes memory) {
return DynamicMetadata.toBytes(attribute);
}
}
And write the test case using the new contract:
...
const DynamicMetadata = await ethers.getContractFactory('DynamicMetadata', owner);
const dynamicMetadataLibrary = await DynamicMetadata.deploy();
await dynamicMetadataLibrary.deployed();
const DynamicMetadataTestContract = await ethers.getContractFactory(
'DynamicMetadataTestContract',
{
signer: owner,
libraries: {
DynamicMetadata: dynamicMetadataLibrary.address,
},
},
);
dynamicMetadataTestContract = await DynamicMetadataTestContract.deploy();
await dynamicMetadataTestContract.deployed();
const attr = {
displayType: 'a',
traitType: 'b',
value: 'c',
};
const attributesInBytes = await dynamicMetadataTestContract.toBytes(attr);
expect(attributesInBytes).to.equal(EXPECTED_VALUE_IN_BYTES);
...
Now we can test the library successfully.
Testing internal functions in solidity contracts
When we want to test internal functions in solidity, for example:
contract GM721 is ERC721, ERC721Burnable {
...
function _beforeBatchMint(address, uint256[] calldata) internal virtual {
...
}
function _batchMint(address to, uint256[] calldata tokenIds) internal {
_beforeBatchMint(to, tokenIds);
...
}
...
}
We have to create a test contract that implements the contract that we want to test:
contract TestGM721 is GM721 {
bool public beforeBatchMintExecuted = false;
...
function _beforeBatchMint(address to, uint256[] calldata tokenIds) internal override(GM721) {
beforeBatchMintExecuted = true;
super._beforeBatchMint(to, tokenIds);
}
function batchMint(address to, uint256[] calldata tokenIds) external {
super._batchMint(to, tokenIds);
}
...
}
In this case, we want to test the batchMint() function and verify that the _beforeBatchMint() is called.
So we define a beforeBatchMintExecuted flag that we can use to check if the function was executed.
Our test ends up looking like this:
...
expect(await testGM721.beforeBatchMintExecuted()).to.be.false;
await testGM721.batchMint(owner.address, [1, 2, 3]);
expect(await testGM721.beforeBatchMintExecuted()).to.be.true;
...