Smart Contract Development
  • Introduction
    • What is a Transaction
    • Accounts and Signing
    • What is a smart contract
  • Learning Solidity
    • Introduction
    • Module 1
      • Variable Types
      • Variable Scope: State & Local variables
      • Global variables
      • Functions
        • View and Pure
        • Shadowing in Fuctions
      • Mapping
      • Require
      • Events
    • Project #1: Simple Registry
    • Module 2
      • Constructor
      • Data Location: Value & Reference
      • Interface
      • Import
        • Importing in Foundry
      • Inheritance
      • ERC-20
      • Checks-effect-interaction pattern
    • Project #2: Basic Vault
    • Module 3
      • Payable
      • Receive
      • Fallback
      • Returns
    • Project #3: ERC20+ETH Wrapper
    • Module 4
      • Immutable and Constant
      • Fixed-point Math
      • Abstract contracts
      • ERC-4626
      • Modifier + Inheritance +Ownable
      • Type
    • Project #4: Fractional Wrapper
    • Module 5
      • If-else
      • Libraries
        • TransferHelper
      • Chainlink Oracle
    • Project #5: Collateralized Vault
  • Compendium
    • Solidity Basics
      • Variable Types
      • Value Types
        • address
        • enum
      • Reference Types
        • strings
        • mappings
        • struct
        • Arrays
        • Multi-Dimensional arrays
      • Global Objects
      • Functions
        • Function types
        • Constructor Function
        • Transaction vs Call
        • Require, Revert, Assert
      • Function signature + selectors
      • Payable
        • Payable + withdraw
        • msg.value & payable functions
      • Receive
      • Fallback function (sol v 0.8)
        • Fallback function (sol v 0.6)
      • call, staticcall, delegatecall
    • Return & Events
    • Control Variable Visibility
    • Local Variables (Storage v Memory)
    • Data Location and Assignment Behaviors
    • Modifiers & Inheritance & Import
      • import styles
    • Interface & Abstract Contracts
    • ABI & Debugging
    • Libraries
    • Conditional(ternary) operators
    • Smart Contract Life-cycle
      • Pausing Smart Contracts
      • Destroying Smart Contracts
    • Merkle Trie and MPT
    • Merkle Tree Airdrop
  • Try & catch
  • Ethereum Signatures
  • EVM, Storage, Opcodes
    • EVM
    • Wei, Ether, Gas
    • Storage
    • ByteCode and Opcodes
    • Transaction costs & Execution costs
  • Reading txn input data
  • Data Representation
  • Yul
    • Yul
      • Intro
      • Basic operations
      • Storage Slots
      • Storage of Arrays and Mappings
      • Memory Operations
      • Memory: how solidity uses memory
      • Memory: Return, Require, Tuples and Keccak256
      • Memory: Logs and Events
      • Inter-contract calls
      • calldata
      • free memory pointer
    • Yul Exercises
      • read state variable
      • read mapping
      • iterate Array, Return Sum
    • memory-safe
  • Upgradable Contracts
    • Upgradability & Proxies
    • UUPS Example
    • Minimal Proxy Example
    • TPP Example
    • 🚧Diamond
      • On Storage
  • Gas Opt
    • Block Limit
    • gasLimit & min cost
    • Solidity Optimiser
    • Memory v calldata
    • Memory caching vs direct storage vs pointers
    • < vs <=
    • reverting early
    • X && Y, ||
    • constant and immutable
    • caching sload into mload
    • Syntactic Sugar
    • using unchecked w/o require
    • Compact Strings
    • Calling a view function
    • Custom errors over require
    • usage of this.
      • multiple address(this)
  • ERCs & EIPs
    • ERC-20.sol
      • Core functions
      • transfer()
      • transferFrom()
      • TLDR transfer vs transferFrom
    • Landing
      • ERC721.sol
      • EIP-721
        • LooksRare
        • Page 1
      • ERC-1271
      • EIP-2981
      • ERC-165
      • EIP-1167: Minimal Proxy Contract
    • VRFConsumerBase
    • UniswapV2Library
  • Yield Mentorship 2022
    • Projects
      • #1 Simple Registry
      • #2 Basic Vault
      • #3 ERC20+ETH Wrapper
        • setFailTransferTrue
      • #4 Fractional Wrapper
      • #5 Collateralized Vault
        • Process
        • Vault.sol
        • Testing
        • Chainlink Oracles
        • Pricing + Decimal scaling
        • Refactor for Simplicity
      • #9 Flash Loan Vault
        • Implementing ERC3156
        • Full code for lender
        • Ex-rate calculation
    • State Inheritance Testing
    • Testing w/ Mocks
    • Yield Style Guide
    • Github Actions
    • TransferHelper.sol
    • math logic + internal fn
    • Interfaces: IERC20
  • Foundry
    • Overview
    • Importing Contracts
    • Testing
      • stdError.arithmeticError
      • assume vs bound
      • Traces
      • label & console2
      • std-storage
  • Smart Contract Security
    • Damn Vulnerable Defi
      • 1. Unstoppable
      • 2. Naive receiver
      • 3. Truster
      • 4. Side Entrance
      • 5. The Rewarder
      • 6. Selfie
      • 7. Compromised
      • 8. Puppet
      • 9. Puppet V2
      • 10 - Free Rider
    • Merkle Tree: shortened proof attack
  • Fixed-Point Math
    • AMM Math
  • Solidity Patterns
    • checks-effects-interactions pattern
    • Router // batch
    • claimDelegate: stack unique owners
    • claimDelegate: cache previous user
  • Array: dup/ascending check
  • Deployment
    • Behind the Scenes
    • Interacting with External Contracts
    • Logging, Events, Solidity, Bloom Filter
  • Misc
    • Mnemonic Phrases
    • Bidul Ideas
  • Archive
    • Brownie Framework
      • Brownie basics
        • storing wallets in .env
        • Deployment to ganache
        • Interacting with contract
        • Unit Testing
        • Testnet deployment
        • Interacting w/ deployed contract
        • Brownie console
      • Brownie Advanced
        • Dependencies: import contracts
        • helpful_scripts.py
        • verify and publish
        • Forking and Mocking
        • Mocking
        • Forking
      • Testing
      • Scripts Framework
        • deploy.py
        • get_accounts
        • deploy_mocks()
        • fund_with_<token>()
      • Brownie Networks
    • Brownie Projects
      • SharedWallet
        • Multiple Beneficiaries
        • Common Code Contract
        • Adding Events
        • Renounce Ownership
        • Separate Files
      • Supply Chain
        • ItemManager()
        • Adding Events
        • Adding unique address to each item
      • Lottery
      • Aave - Lending and Borrowing
        • Approve & Deposit
        • Borrow
      • NFT
      • Advanced Collectible
        • adv_deploy() + Testing
        • Create Metadata
        • Setting the TokenURI
    • node npm
    • Ganache
    • Truffle
    • Remix
    • Installing Env
Powered by GitBook
On this page
  • Test updating storage variable
  • Tips
  • get accounts
  • Negative tests
  • How can we conduct a negative test properly?
  • Where should i run my tests
  1. Archive
  2. Brownie Framework
  3. Brownie basics

Unit Testing

  • create python test files in tests folder.

  • filenames must begin with test_

  • from brownie import <contract_name>, accounts

In this following example, we want to test that favNum initializes to 0.

  • deploy the contract

  • retrieve the starting value

  • compare it with expected starting value

from brownie import SimpleStorage, accounts

def test_deploy():
    # Arrange - setup
    account = accounts[0]

    # Act
    simple_storage = SimpleStorage.deploy({"from": account})
    starting_value = simple_storage.retrieve()
    expected_value = 0

    # Assert
    assert expected_value == starting_value

To run this test, input: brownie test in terminal:

If we get a green dot at the end like above, all tests were successful.

Test updating storage variable

from brownie import SimpleStorage, accounts

def test_deploy():
    # Arrange - setup
    account = accounts[0]

    # Act
    simple_storage = SimpleStorage.deploy({"from": account})
    starting_value = simple_storage.retrieve()
    expected_value = 0

    # Assert
    assert expected_value == starting_value

def test_update_storage():
    # Arrange - setup
    account = accounts[0]
    simple_storage = SimpleStorage.deploy({"from": account})

    # Act
    expected = 15
    simple_storage.store(expected,{"from": account}) #store 15

    # Assert
    assert expected == simple_storage.retrieve()
  • deploy contract

  • update favNum storage variable to be 15

  • retrieve storage variable and compare with expected.

Tips

To test 1 function

brownie test -k <function name> brownie test -k test_update_storage

To get into interactive mode:

brownie test --pdb

when a test fails, or an error is encountered, terminal will convert to an interactive python shell:

we can observe the values of variables by typing them in and their values will be returned.

reference the other brownie flags from pytest documentation

get accounts

from brownie import SimpleStorage, accounts, network

#if on non-persistent dev netowrk, use generated ganache account
#else look in.yaml file for our MM account's PK
def get_accounts():
    if network.show_active() == "development":
        return accounts[0]                  #use ganache generated account.
    else:
        return accounts.add(config["wallets"]["wallet1"])  #look in config.yaml
        
def test_deploy():
    # Arrange - setup
    account = get_accounts()

#etc

Negative tests

Lets say you want to do a test that results in a revert. Like testing a function that only the owner can call, but calling it from a non-owner address.

We will use pytest to skip tests that we only want conducted in our private local chains (pip install pytest)

test_fund_me.py
def test_OwnerWithdraw():
    if network.show_active() not in LOCAL_BLOCKCHAIN_ENV:
       pytest.skip("ONLY FOR LOCAL TESTING!")

    if len(FundMe) <= 0: 
        fund_me = deploy_fund_me()
    fund_me = FundMe[-1]

    bad_actor = accounts.add()
    fund_me.withdraw({"from": bad_actor})

You would expect a revert to be thrown, like below, reflecting that an exception had occurred:

How can we conduct a negative test properly?

the correct form of the test would be:

from brownie import exceptions

def test_only_owner_can_withdraw():
    if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
        pytest.skip("only for local testing")
    fund_me = deploy_fund_me()
    bad_actor = accounts.add()
    with pytest.raises(exceptions.VirtualMachineError):
        fund_me.withdraw({"from": bad_actor})

Where should i run my tests

  1. Brownie Ganache chain with Mocks: Always

    1. (tests should clear on brownie spun up ganache-cli env)

  2. Testnet: Always (but only for integration testing)

  3. Brownie mainnet-fork: Optional

  4. Custom mainnet-fork: Optional

  5. Self/Local Ganache: Not necessary, but good for tinkering

PreviousInteracting with contractNextTestnet deployment

Last updated 3 years ago

exception thrown