How to use fixtures in Brownie unit tests for Solidity smart contracts

In this tutorial, we are going to learn how to use fixtures in Brownie unit tests for Solidity smart contracts so we avoid repeated code and set up tests quicker.

In this tutorial, we are going to learn how to use fixtures in Brownie unit tests for Solidity smart contracts so we avoid repeated code and set up tests quicker.

Fixtures allow you to run code before every test to set up your tests and have the network and your smart contract in a specific state to run your tests.

It allows you to avoid repeated code and it enhances the speed of your tests. Depending on how you write your fixtures, you can make it so when the fixture is called for the first time, the state of the network and the smart contract will be saved. The next time you use the fixture, it will not re-run the code but use the saved state after it ran for the first time.

To turn a simple function into a fixture, we use the pytest.fixture decorator:

import pytest
from brownie import MyContract, accounts

# create a fixture
@pytest.fixture
def contract():
    # if the contract was deployed before, don't redeploy it, just return it
    if len(MyContract) == 0:
        return MyContract.deploy({"from": accounts[-1]})
    return MyContract[len(MyContract) - 1]

# example of test using the deploy_contract fixture:
def test_initialised_with_the_correct_values(contract):
    assert contract.myValue() == 1

In the example above, we create a function called contract and turn it into a fixture using the @pytest.fixture decorator.

Then, we use that fixture in the test called test_initialised_with_the_correct_values by passing the fixture in the parameters of the test function.

You can use the returned value of the fixture by using the name of the function as a variable that contains the returned value.

For example, our fixture called contract returns the deployed smart contract so I can just use contract to interact with the deployed smart contract.

It's that simple and yet very powerful!

You can also pass extra parameters to your fixtures like the scope parameter that allows you to tell when the fixture will be executed:

import pytest
from brownie import MyContract, accounts

@pytest.fixture(scope="module")
def contract():
    return MyContract.deploy({"from": accounts[-1]})

def test_initialised_with_the_correct_values(contract):
    assert contract.myValue() == 1

def test_transfer_function(contract):
    contract.transfer()
    assert contract.myValue() == 0

By passing scope=module to the fixture, it will only be executed once for both tests and both tests will use the same contract. That means the state of the smart contract is carried over from a test to another so be careful when doing this parameter.

The possible values for scope are: function, class, module, or session.

You can also use fixtures inside other fixtures:

import pytest
from brownie import MyNFT, accounts

@pytest.fixture
def contract():
    return MyNFT.deploy({"from": accounts[-1]})

@pytest.fixture
def contract_with_open_mint():
    return MyNFT.deploy({"from": accounts[-1]})

def test_initialised_with_the_correct_values(contract):
    assert contract.maxSupply() == 10000

def test_mint_function(contract_with_open_mint):
    contract_with_open_mint.mint()
    assert contract_with_open_mint.tokenCoutner() == 1

That way, you can use either one of the fixtures depending on the test and create fixtures that take your smart contract to a specific state.

And that's it 🎉

Thank you for reading this article