在上一篇中我们搭建了一个可 Mint 的最小可运行 NFT 合约,本篇文章将进一步完善合约功能、优化代码结构,并手把手带你完成 Remix 本地单元测试 与 以太坊主网测试网部署前的全部准备。关键词:Remix 合约测试、Solidity 单元测试、ERC721 铸造方法、NFT 合约示例、eth 支付、JavaScript 测试框架。
继续升级合约
六个关键改动
- 将构造函数形参
initialOwner移除,使用部署者地址直接作为合约 owner;后续部署无需额外输入。 - 新增私有变量
_nextTokenId(类型uint256),记录下一个将被铸造的 NFT ID,避免重复。 - 重新定义
mint(uint256 quantity),暂时限制每调用仅铸造1枚。 - 删除
onlyOwner修饰符,允许任何用户自由铸造。 - 添加
payable修饰符,铸造需支付 0.01 ether 作为费用,体验真实经济模型。 - 使用
_mint替换_safeMint,移除to形参,改为msg.sender,降低 Remix IDE 内部调用警告。
代码差异对比
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC721, Ownable {
+ uint256 private _nextTokenId = 0;
- constructor(address initialOwner)
+ constructor() ERC721("MyToken", "MTK") Ownable(msg.sender) {}
- function safeMint(address to, uint256 tokenId) public onlyOwner {
+ function mint(uint256 quantity) public payable {
+ require(quantity == 1, "quantity must be 1");
+ require(msg.value == 0.01 ether, "must pay 0.01 ether");
+ uint256 tokenId = _nextTokenId++;
- _safeMint(to, tokenId);
+ _mint(msg.sender, tokenId);
}
}提醒
private:仅合约内部可读写public:链上任意地址可读、合约内可写
这样的区分能帮助你在复杂业务中快速定位变量作用域。
拓展建议:如果想开放批量 Mint(quantity>1),可把 _nextTokenId 改为 for 循环,并增加 gas 上限检查,以防 DoS。
使用 Remix 单元测试插件
1. 激活插件
打开 Remix 左侧面板底部的「Plugin Manager」,搜索关键字 unit 激活 SOLIDITY UNIT TESTING 插件;图标将固定在左侧导航栏。
👉 掌握这一步,可视化 Solidity 自动测试轻松启动!
2. 理解四个钩子函数
Remix 提供与传统 JavaScript 测试框架类似的钩子函数:
beforeAll():所有测试前运行一次beforeEach():每次测试前运行afterEach():每次测试后运行afterAll():所有测试后运行一次
利用这些钩子,可把合约部署、资金初始化、状态重置等逻辑统一管理,测试代码更简洁。
3. 查看自动生成的测试模板
若使用 Remix NFT 项目模板,tests/MyToken_test.sol 会被提前创建。如为空白工作区,点击 Generate 按钮即可迅速生成测试文件,零门槛上手。
编写 & 运行 Solidity 单元测试
引入与初始化
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import "remix_tests.sol";
import "remix_accounts.sol";
import "../contracts/MyToken.sol";
contract MyTokenTest {
MyToken s; // 待测试合约实例
address acc0; // 快速获取测试账户
function beforeAll () public {
s = new MyToken();
acc0 = TestsAccounts.getAccount(0);
}
}测试 1:校验 name & symbol
function testTokenNameAndSymbol () public {
Assert.equal(s.name(), "MyToken", "token name did not match");
Assert.equal(s.symbol(), "MTK", "token symbol did not match");
}测试 2:校验 Mint 逻辑
这里是重点:为防止调用失败,测试函数需附加 #value: 10000000000000000(10¹⁶ wei = 0.01 ether)。
/// #value: 10000000000000000
function testMint() public payable {
uint256 balanceBefore = s.balanceOf(address(this));
s.mint{value: msg.value}(1);
uint256 balanceAfter = s.balanceOf(address(this));
Assert.equal(balanceAfter - balanceBefore, 1, "balance mismatch after mint");
}调试技巧:如果使用 Remix VM,可点击「Debug」按钮查看call stack、memory、storage变化,深入理解 payble 工作机制。
执行测试:
选择文件 → 点击 Run → Remix 会启动独立测试链环境,运行结束弹出绿色 ✓ 代表通过。若出现红 ✗,根据失败提示快速定位代码或测试流程问题即可。
FAQ
Q1:为什么 Mint 方法改用 _mint 后不再报 “ERC721: transfer to non ERC721Receiver” 警告?
A1:_safeMint 会检测接收地址是否实现 IERC721Receiver 接口;Remix 测试时收件地址是合约自身,未实现接口容易触发回退。改用 _mint 移除安全检查即可绕过,注意在生产环境需要自行验证收件地址合法性。
Q2:每次 Mint 都设置固定 0.01 ether 合理吗?如果想做分级定价怎么办?
A2:当然可以通过 require(msg.value >= price * quantity, "Need more ETH") + mapping 分级来实现,但务必在合约层面控制变量精度(如用 wei 参与运算)以防精度误差。
Q3:Solidity 测试与 JavaScript(Chai+Mocha)测试如何选择?
A3:Solidity 单元测试运行更快、易于覆盖高频核心业务;JavaScript 测试则更适合在复杂 DApp 场景中结合前端模拟用户交互。最佳实践为 双轨并行:合约用 Solidity 做单元,前端用 JS 做集成。
Q4:Remix VM 和真实测试网有什么关系?
A4:Remix VM 是本地内存级别的链,不会把数据真正写到主网;部署到 Sepolia、Mumbai 等测试网时,须切换 Injected Web3 环境并准备相应水龙头 ETH / MATIC。
Q5:除了 Remix,还有哪些 IDE 支持一键跑 Solidity 单元测试?
A5:Hardhat、Foundry、Truffle 均支持自动测试并配合 CI 集成。未来可考虑把代码迁移到 Foundry,以享受更高效的 forge test 并行执行特性。
用 JavaScript 进行多链环境集成测试(可选)
在 Remix 工作区新建 scripts/mint.test.js,示例用 Chai + Mocha 语法:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
it("should mint 1 NFT and change balance", async function () {
const [owner] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy();
await token.deployed();
await token.mint(1, { value: ethers.utils.parseEther("0.01") });
expect(await token.balanceOf(owner.address)).to.equal(1);
});
});👉 点击深入了解 .sol + .js 测试双剑合璧的完整流程!
完成后,运行 npx hardhat test 或右键 Run 即可看到终端打印绿色 ✓,在 CI 中集成毫无压力。
下一步:编译并部署
至此,代码逻辑已完善,本地测试通过,距离真实链上部署只差「合约编译 + Gas 估算 + 网络选择」。
如果你准备将 NFT 部署到以太坊 Sepolia、Polygon Amoy 或 BSC Testnet,请预留:
- 水龙头代币(可搜索「Sepolia Faucet」)
- Remix「Deploy & Run」面板中选择 Injected Provider
- 确认构造函数无需参数即可直接部署
祝你一路绿灯——下篇我们会分享使用 Ant Design Web3 组件 + React 的前端交互,做到页面一键连接钱包 & 立即 Mint,敬请期待!