Testing Smart Contracts with Foundry
Testing in Foundry works different than in Truffle or Hardhat. The tests in Foundry are written in Solidity. Now youāre probably wondering, how can you test several sender accounts in Foundry?
That works with VM Cheatcodes.
Letās add a simple test and see how they perform.
Adding a test in Foundry
Add the following file to test/Spacebear.test.sol
pragma solidity 0.8.10;
import "forge-std/Test.sol";
import "../src/Spacebears.sol";
contract SpacebearsTest is Test {
Spacebear spacebear;
function setUp() public {
spacebear = new Spacebear();
}
function testNameIsSpacebear() public {
assertEq(spacebear.name(), "Spacebear");
}
}
then run
forge test
Minting an NFT in Foundry
Letās mint our NFT and see if weāre the rightful owner. Add the following parts to the test:
pragma solidity ^0.8.4;
import "forge-std/Test.sol";
import "../src/Spacebears.sol";
contract SpacebearsTest is Test {
Spacebear spacebear;
function setUp() public {
spacebear = new Spacebear();
}
function testNameIsSpacebear() public {
assertEq(spacebear.name(), "Spacebear");
}
function testMintingNFTs() public {
spacebear.safeMint(msg.sender);
assertEq(spacebear.ownerOf(0), msg.sender);
assertEq(spacebear.tokenURI(0), "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacebear_1.json");
}
}
If you run that test, it will surprisingly fail.
But you donāt know where. If you run the command with -vv in addition, you can get insights (going up to -vvvv):
forge test -vv
Interestingly it fails at generating the URI, it completely ignores the uint. It works, interestingly, in truffle and hardhat though. Letās add an explicit String conversion. Luckily thatās fairly easy with openzeppelin. Add the following to src/Spacebears.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "openzeppelin-contracts/token/ERC721/ERC721.sol";
import "openzeppelin-contracts/access/Ownable.sol";
import "openzeppelin-contracts/utils/Counters.sol";
import "openzeppelin-contracts/utils/Strings.sol";
contract Spacebear is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("Spacebear", "SBR") {}
function _baseURI() internal pure override returns (string memory) {
return "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/";
}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
function buyToken() public payable {
uint256 tokenId = _tokenIdCounter.current();
require(msg.value == tokenId * 0.1 ether, "Not enough funds sent");
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
}
// The following functions are overrides required by Solidity.
function _burn(uint256 tokenId) internal override(ERC721) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
pure
override(ERC721)
returns (string memory)
{
return string(abi.encodePacked(_baseURI(),"spacebear_",Strings.toString(tokenId+1),".json"));
}
}
run the test again, and it will pass:
Testing NFT Transfers with VM Pranking in Foundry
So, how do you test the transfer from address 1 to address 2?
With VM Pranking.
VM Pranking is a special contract command that instructs the VM to use a different account as msg.sender for example. vm.prank(address)
would send the next transaction with the address given. If you do vm.startPrank(address)
you can send several instructions with a new address
Let me show youā¦
pragma solidity ^0.8.4;
import "forge-std/Test.sol";
import "../src/Spacebears.sol";
contract SpacebearsTest is Test {
Spacebear spacebear;
function setUp() public {
spacebear = new Spacebear();
}
function testNameIsSpacebear() public {
assertEq(spacebear.name(), "Spacebear");
}
function testMintingNFTs() public {
spacebear.safeMint(msg.sender);
assertEq(spacebear.ownerOf(0), msg.sender);
assertEq(spacebear.tokenURI(0), "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacebear_1.json");
}
function testNftCreationWrongOwner() public {
vm.startPrank(address(0x1));
vm.expectRevert("Ownable: caller is not the owner");
spacebear.safeMint(address(0x1));
vm.stopPrank();
}
}
That works, but how can you buy an NFT. pranking is just setting the address to another account - how do you get balance? In testing you can use the same cheatcode prank, while in anvil youād use another VM cheatcode, called hoax(address), there you get some ether too. Here we can keep using prank
pragma solidity ^0.8.4;
import "forge-std/Test.sol";
import "forge-std/Vm.sol";
import "../src/Spacebears.sol";
contract SpacebearsTest is Test {
Spacebear spacebear;
function setUp() public {
spacebear = new Spacebear();
}
function testNameIsSpacebear() public {
assertEq(spacebear.name(), "Spacebear");
}
function testMintingNFTs() public {
spacebear.safeMint(msg.sender);
assertEq(spacebear.ownerOf(0), msg.sender);
assertEq(spacebear.tokenURI(0), "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacebear_1.json");
}
function testNftCreationWrongOwner() public {
vm.startPrank(address(0x1));
vm.expectRevert("Ownable: caller is not the owner");
spacebear.safeMint(address(0x1));
vm.stopPrank();
}
function testNftBuyToken() public {
vm.startPrank(address(0x1));
spacebear.buyToken();
vm.stopPrank();
assertEq(spacebear.ownerOf(0), address(0x1));
}
}
If you run the test, it should just pass. But how can you deploy the token to Gƶrli? Thatās what we gonna do in the next lectureā¦