I’m working on writing tests for my NFT contract using HardHat. I need to test the complete flow of creating, minting, and moving NFTs between accounts.
Here’s what I have so far:
describe("NFT Transfer Testing", function () {
it("Should successfully transfer NFT between accounts", async function () {
const { marketplace, deployer, userAccount } = await loadFixture(setupFixture);
const { nftContract, owner: nftOwner } = await loadFixture(setupNFTFixture);
await nftContract.connect(userAccount)
await nftContract.createToken(userAccount.address)
console.log(await nftContract.balanceOf(userAccount.address));
console.log(await nftContract.ownerOf(1));
console.log("marketplace address: ", marketplace.target)
console.log("deployer address: ", deployer.address)
await nftContract.connect(userAccount)
await nftContract.transferFrom(userAccount.address, marketplace.target, 1);
console.log(await nftContract.balanceOf(marketplace.target));
console.log(await nftContract.ownerOf(1));
})
})
The transfer operation throws this error: Error: VM Exception while processing transaction: reverted with reason string 'ERC721: caller is not token owner or approved'
My console output shows:
1n
userAccount.address: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
marketplace.target: 0x5FbDB2315678afecb367f032d93F642f64180aa3
deployer.address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
msg.sender: 0xf39Fd6e51aad88F6F4ce6ab8827279cfffb92266
Even though I use connect(userAccount), the transaction sender remains the deployer account. I tried adding approval first:
await nftContract.connect(userAccount)
await nftContract.approve(marketplace.target, 1)
await nftContract.transferFrom(userAccount.address, marketplace.target, 1);
But I get: Error: VM Exception while processing transaction: reverted with reason string 'ERC721: approve caller is not token owner or approved for all'
How can I properly switch the transaction sender to execute these operations from the correct account?
The issue is in your method chaining syntax. When you write await nftContract.connect(userAccount) followed by await nftContract.createToken(userAccount.address) on separate lines, the second call doesn’t use the connected signer. You need to chain them properly or assign the connected contract to a variable.
Try this approach:
const userConnectedContract = nftContract.connect(userAccount);
await userConnectedContract.createToken(userAccount.address);
// ... later
await userConnectedContract.approve(marketplace.target, 1);
await userConnectedContract.transferFrom(userAccount.address, marketplace.target, 1);
Alternatively, chain the calls directly:
await nftContract.connect(userAccount).createToken(userAccount.address);
This should resolve the sender mismatch you’re experiencing. The connect() method returns a new contract instance with the specified signer, but you need to actually use that returned instance for subsequent transactions.
another thing to check - make sure your fixtures are returning the same contract instances. ive seen cases where mixing setupFixture and setupNFTFixture creates seperate contract deployments which can mess up the signer context. try using just one fixture for everything or atleast verify both fixtures point to same deployed contract address.
oh this is a clasic hardhat gotcha! i’ve been there before 
So you’re seeing the deployer address in your console output even though you think you’re using connect(userAccount) - that’s because the connection isn’t persisting the way you expect. The previous answer nailed the syntax issue, but i’m curious about something else…
are you sure your createToken function is actually minting the NFT to the userAccount address? because if the deployer is still the one calling createToken, the NFT might be getting minted to the deployer instead of the userAccount, which would explain why the transfer fails later.
Have you tried checking who actually owns the token right after minting? something like:
const userConnectedContract = nftContract.connect(userAccount);
await userConnectedContract.createToken(userAccount.address);
console.log("actual owner:", await nftContract.ownerOf(1));
console.log("expected owner:", userAccount.address);
also, what does your createToken function look like in the contract? does it take an address parameter and mint to that address, or does it mint to msg.sender? because if it’s minting to msg.sender and you’re not connecting properly, that could be the root cause.
and one more thing - are you using the same nftContract instance in both your setupFixture and setupNFTFixture? sometimes mixing fixtures can cause weird behavior with signers.