DEV Community

ilija
ilija

Posted on • Updated on

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

Top comments (1)

Collapse
 
rasbak_b profile image
rasbak stars

message me now on fiverr.com/rasbak_stars