Contract Development and Testing

·

Smart contract development is a foundational skill in the Web3 ecosystem, especially for creators and developers building decentralized applications (dApps) and NFT projects. In this guide, we’ll walk through refining an ERC721 NFT contract, writing unit tests using Remix IDE, and preparing for deployment. Whether you're building digital collectibles or tokenized assets, mastering contract logic and testing ensures reliability, security, and scalability.

We’ll build upon a basic ERC721 contract by enhancing its minting functionality, removing unnecessary restrictions, and introducing payable features—making it more accessible and user-friendly.

Enhancing the NFT Contract

In the previous tutorial, we created a simple ERC721 contract with a safeMint function. Now, let’s improve it by implementing a new mint method that simplifies user interaction while maintaining core functionality.

The updated contract will:

Here’s the revised Solidity code:

// 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() ERC721("MyToken", "MTK") Ownable(msg.sender) {}

    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++;
        _mint(msg.sender, tokenId);
    }
}
The private keyword restricts variable or function access to within the contract itself, while public allows external interaction.

This streamlined approach improves usability: users can mint directly without needing approval or special permissions, and the payable feature introduces a basic economic model—ideal for real-world NFT drops.

👉 Discover how blockchain platforms streamline smart contract deployment and testing.

Writing Unit Tests in Remix IDE

Testing is crucial to ensure your contract behaves as expected before deploying to the blockchain. Remix IDE provides built-in tools for writing and running unit tests in Solidity.

Step 1: Install the Unit Testing Plugin

Navigate to the bottom-left corner of Remix and click on the Plugin Manager icon. Search for "unit" and locate SOLIDITY UNIT TESTING. Click Activate to install it.

Once activated, the plugin appears in the left sidebar. Clicking it opens the testing interface where you can run and monitor test results.

Step 2: Create and Structure Test Files

Remix supports special test functions that help organize your test suite:

The template generates a test file at tests/MyToken_test.sol. If not present, use the Generate button in Remix to create one.

Key components in our test file:

Below is the complete test script:

// 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";

MyToken s;
address acc0;

function beforeAll() public {
    s = new MyToken();
    acc0 = TestsAccounts.getAccount(0);
}

function testTokenNameAndSymbol() public {
    Assert.equal(s.name(), "MyToken", "token name did not match");
    Assert.equal(s.symbol(), "MTK", "token symbol did not match");
}

/// #value: 10000000000000000
function testMint() public payable {
    s.mint{value: msg.value}(1);
    Assert.equal(s.balanceOf(address(this)), 1, "balance did not match");
}
The #value comment specifies the amount of Ether (in wei) sent during the test—here set to 0.01 ether.

Step 3: Run the Tests

Select the test file in the file explorer and click Run in the Solidity Unit Testing panel. The test executes in an isolated environment, simulating blockchain behavior.

Upon completion, Remix displays a summary showing passed or failed assertions. A successful run confirms your contract logic works as intended.

Advanced Testing with JavaScript (Mocha & Chai)

While Solidity-based tests are convenient, developers familiar with JavaScript frameworks like Mocha and Chai can also write tests in .js files.

Mocha provides a robust structure for asynchronous testing, and Chai offers expressive assertions. Remix supports this workflow:

  1. Create a scripts/ folder.
  2. Add a .js file (e.g., mintTest.js).
  3. Write tests using Web3.js or Ethers.js to interact with your contract.
  4. Right-click and select Run to execute.

Example:

const MyToken = artifacts.require("MyToken");

contract("MyToken", (accounts) => {
  it("should mint one NFT", async () => {
    const instance = await MyToken.deployed();
    const tx = await instance.mint(1, { value: web3.utils.toWei('0.01', 'ether'), from: accounts[0] });
    const balance = await instance.balanceOf(accounts[0]);
    assert.equal(balance.toNumber(), 1, "Balance should be 1");
  });
});

This flexibility allows teams to choose their preferred testing style—whether staying fully on-chain with Solidity or leveraging familiar JavaScript tooling.

👉 Explore secure environments for deploying and testing smart contracts on leading blockchain platforms.

Core Keywords for SEO Optimization

To align with search intent and improve visibility, we’ve naturally integrated the following core keywords throughout this guide:

These terms reflect common queries from developers entering the Web3 space, ensuring relevance for both beginners and intermediate builders.

Frequently Asked Questions (FAQ)

What is the difference between _mint and _safeMint?

_mint directly assigns a token ID to an address without checking if the recipient can handle ERC721 tokens. _safeMint includes an additional check to verify that the receiving contract implements the required ERC721Receiver interface, preventing accidental loss of NFTs.

Why make the mint function payable?

Adding payable allows users to send Ether when calling mint(), enabling monetization of NFT drops. It’s ideal for funding project development or creating scarcity through pricing mechanisms.

Can I test contracts without deploying them?

Yes! Remix simulates a local blockchain environment where you can compile, test, and debug contracts without deploying them on a live network—saving time and gas fees during development.

How do I handle multiple NFT mints per transaction?

Currently, our logic limits mints to one per call (require(quantity == 1)). To allow bulk mints, remove or adjust this condition and loop through multiple _mint calls—while considering gas limits.

Is Remix suitable for large-scale projects?

Remix is excellent for learning, prototyping, and small-to-medium projects. For complex dApps, consider using Hardhat or Foundry for advanced scripting, debugging, and CI/CD integration.

What happens if someone sends incorrect Ether value?

The require(msg.value == 0.01 ether) condition reverts the transaction if the sent amount isn’t exactly 0.01 ETH, protecting against underpayments or overpayments unless you implement refund logic.

👉 Get started with trusted tools for secure smart contract creation and execution.