DEV Community

ilija
ilija

Posted on • Edited on

2

Web3 backend and smart contract development for Python developers Musical NFTs part 7: writing tests for smart contracts

Brownie relay on Pytest for smart contracts testing. For testing purpose we will need only two new files inside: ./tests folder: conftest.py and test_contracts.py

helpers.py file have (we already create for deployment purpose) one function which returns eth acocunts from which we will deploy our contracts inisde our test.

conftest.py is used to set-up initial conditions in which we can test some smart-contract functionality. For this purpose we will use @pytest.fixtures decorators. This will allow us to pass them as argument to our test and on this way to set-up intial conditions in which we can test our smart contract functionality. Here is code for conftest.py (more explanation in comments):

#!/usr/bin/python3

import pytest

#function for loading account
from scripts.helpers import get_account


initial_supply = 1000
max_nft = 100

"""
Defining a Shared Initial State for this test. A common pattern in testing is to include one or more module-scoped setup fixtures that define the 
initial test conditions, and then use fn_isolation (next step) to revert to this base state at the start of each test. 
"""


@pytest.fixture(scope="module", autouse=True)
def initial_supply_of_mock_tokens():
    # return intial_supplay of MockUSDC tokens
    return initial_supply



@pytest.fixture(scope="module", autouse=True)
def smartContract_deploy(MusicNFT, MockUSDC):
    account = get_account()
    # deploy MockUSDC contract
    mock_token = MockUSDC.deploy(initial_supply, {"from": account[0]})
    # deploy MusciNFT contract
    musicNFTtoken = MusicNFT.deploy(mock_token, 10, {"from": account[0]})
    # return depoyment addresses of both contract. What deploy method return is much more richer, but in this moment we use just address
    return mock_token, musicNFTtoken


"""
In many cases we want to isolate our tests from one another by resetting the local environment. Without isolation, it is possible that the outcome of 
a test will be dependent on actions performed in a previous test. This is done by following function. 
"""


@pytest.fixture(scope="function", autouse=True)
def isolate(fn_isolation):
    # perform a chain rewind after completing each test, to ensure proper isolation
    # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html #isolation-fixtures
    pass


@pytest.fixture(scope="module")
def test_accounts():
    account_1, account_2 = get_account()
    return account_1, account_2
Enter fullscreen mode Exit fullscreen mode

What we miss now is just test_contract.py file. Here si more details

#!/usr/bin/python3

# Test if deployment whent well and if address pf deployed contracts starts with 0x
def test_initial_supplay_account_alice(smartContract_deploy):
    mock_token, musicNFTtoken = smartContract_deploy
    assert mock_token.address.startswith("0x") 
    assert musicNFTtoken.address.startswith("0x") 


# Test if right ammount of MockTokens are minted to deployer/owner address
def test_mock_token(initial_supply_of_mock_tokens, smartContract_deploy, test_accounts):
    mock_token, musicNFTtoken =  smartContract_deploy
    deployer_account, user_account = test_accounts
    initial_supply = initial_supply_of_mock_tokens
    mock_token_balance = mock_token.balanceOf(deployer_account)
    assert mock_token_balance == 1000_000_000_000_000_000_000 


# Test approve & allowance functionality. Reason for this is fact that when we have crypto 
# buyers for NFTs we will need to transfer from our user to our smart contract certain 
# amount of USDC tokens. And because we will call USDC contract from our MusicNFT contract 
# we will need to have rights to spend user USDC by transfering from his account to 
# our account. 
def test_mock_token_approve_allow(smartContract_deploy, test_accounts):
    accountOne, accountTwo = test_accounts
    mock_token, musicNFTtoken =  smartContract_deploy
    mock_token.approve(musicNFTtoken.address, 1000)
    result = mock_token.allowance(accountOne, musicNFTtoken.address)
    assert result == 1000


# And now finally let's test our NFT contract. And here in this test we will check whole buy with crypto flow. 
# And this includes: buyer approve our NFT contract to spend USDC 
# for NFT in this name. 
def test_NFT_buy_with_crypto(smartContract_deploy, test_accounts):
    mock_token, musicNFTtoken =  smartContract_deploy
    deployer_account, user_account = test_accounts
    mock_token.approve(musicNFTtoken.address, 1000)
    result = mock_token.allowance(deployer_account, musicNFTtoken.address)
    assert result == 1000
    mock_token.transfer(musicNFTtoken.address, 1000)
    mock_token_balance = mock_token.balanceOf(musicNFTtoken.address)
    assert mock_token_balance == 1000
Enter fullscreen mode Exit fullscreen mode

Let's run test

$brownie test -v

=============================================================================== 4 passed in 2.54s ================================================================================
Terminating local RPC client...
(env)  brownie_musical_nfts/tests git:(main) ✗ ➜ brownie test -v
Brownie v1.19.3 - Python development framework for Ethereum

============================================================================== test session starts ===============================================================================
platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/env/bin/python3
cachedir: tests/.pytest_cache
hypothesis profile 'brownie-verbose' -> verbosity=2, deadline=None, max_examples=50, stateful_step_count=10, report_multiple_bugs=False, database=DirectoryBasedExampleDatabase(PosixPath('/home/ilija/.brownie/hypothesis'))
rootdir: /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts
plugins: eth-brownie-1.19.3, forked-1.4.0, hypothesis-6.27.3, web3-5.31.3, xdist-1.34.0
collected 4 items                                                                                                                                                                
This version of µWS is not compatible with your Node.js build:

Error: Cannot find module './uws_linux_x64_111.node'
Falling back to a NodeJS implementation; performance may be degraded.



Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 20 --hardfork istanbul --wallet.mnemonic attitude grant adjust accuse mail visual hammer potato nest interest breeze crime --wallet.defaultBalance 200'...

test_contracts.py::test_initial_supplay_account_alice PASSED                                                                                                               [ 25%]
test_contracts.py::test_mock_token PASSED                                                                                                                                  [ 50%]
test_contracts.py::test_mock_token_approve_allow PASSED                                                                                                                    [ 75%]
test_contracts.py::test_NFT_buy_with_crypto PASSED                                                                                                                         [100%]

=============================================================================== 4 passed in 2.41s ================================================================================
Enter fullscreen mode Exit fullscreen mode

In this moment we should have our smart-ontract written, tested and deployed. Next step is integration with Django backend.

Code can be found in github repo

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (1)

Collapse
 
rasbak_b profile image
rasbak stars

message me now on fiverr.com/rasbak_stars

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay