From e6bfd262b405fa53aa0406d0bd6ef3cbc09d276c Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 11 Apr 2019 15:11:48 -0400 Subject: [PATCH] Add documentation on how to do unit tests with web3.py --- docs/examples.rst | 22 +++++ tests/core/contracts/test_contract_example.py | 97 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 tests/core/contracts/test_contract_example.py diff --git a/docs/examples.rst b/docs/examples.rst index 6ab75b6d6e..24da94477d 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -449,3 +449,25 @@ When someone has an allowance they can transfer those tokens using the .. _ERC20: https://github.com/ethereum/EIPs/blob/7f4f0377730f5fc266824084188cc17cf246932e/EIPS/eip-20.md + + +Contract Unit Tests in Python +----------------------------- + +Here is an example of how one can use the pytest platform in python,web3.py, +eth-tester, and pyevm to perform unit tests entirely in python without any +additional need for a full featured ethereum node/client. To install needed +dependencies you can use the pinned extra for eth_tester in web3 and pytest: + +.. code-block:: bash + pip install web3[tester] pytest + +Once you have an environment set up for testing, you can then write your tests +like so: + +.. include:: ../tests/core/contracts/test_contract_example.py + :code: python + :start_line: 1 + + + diff --git a/tests/core/contracts/test_contract_example.py b/tests/core/contracts/test_contract_example.py new file mode 100644 index 0000000000..1e4820bd42 --- /dev/null +++ b/tests/core/contracts/test_contract_example.py @@ -0,0 +1,97 @@ +# This file is used by the documentation as an example of how to write unit tests with web3.py +import logging +import pytest + +from web3 import ( + EthereumTesterProvider, + Web3, +) + + +@pytest.fixture +def tester_provider(): + return EthereumTesterProvider() + + +@pytest.fixture +def eth_tester(tester_provider): + return tester_provider.ethereum_tester + + +@pytest.fixture +def w3(tester_provider): + return Web3(tester_provider) + + +@pytest.fixture +def deploy_contract(eth_tester, w3): + # For simplicity of this example we statically define the + # contract code here. You might read your contracts from a + # file, or something else to test with in your own code + # + # pragma solidity^0.5.3; + # + # contract Foo { + # + # string public bar; + # event barred(string _bar); + # + # constructor() public { + # bar = "hello world"; + # } + # + # function setBar(string memory _bar) public { + # bar = _bar; + # emit barred(_bar); + # } + # + # } + + deploy_address = str(eth_tester.get_accounts()[0]) + + abi = """[{"anonymous":false,"inputs":[{"indexed":false,"name":"_bar","type":"string"}],"name":"barred","type":"event"},{"constant":false,"inputs":[{"name":"_bar","type":"string"}],"name":"setBar","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":true,"inputs":[],"name":"bar","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]""" # noqa: E501 + # This bytecode is the output of compiling with + # solc version:0.5.3+commit.10d17f24.Emscripten.clang + bytecode = """608060405234801561001057600080fd5b506040805190810160405280600b81526020017f68656c6c6f20776f726c640000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6103bb806101166000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806397bc14aa14610058578063febb0f7e14610113575b600080fd5b6101116004803603602081101561006e57600080fd5b810190808035906020019064010000000081111561008b57600080fd5b82018360208201111561009d57600080fd5b803590602001918460018302840111640100000000831117156100bf57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610196565b005b61011b61024c565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561015b578082015181840152602081019050610140565b50505050905090810190601f1680156101885780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101ac9291906102ea565b507f5f71ad82e16f082de5ff496b140e2fbc8621eeb37b36d59b185c3f1364bbd529816040518080602001828103825283818151815260200191508051906020019080838360005b8381101561020f5780820151818401526020810190506101f4565b50505050905090810190601f16801561023c5780820380516001836020036101000a031916815260200191505b509250505060405180910390a150565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102e25780601f106102b7576101008083540402835291602001916102e2565b820191906000526020600020905b8154815290600101906020018083116102c557829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061032b57805160ff1916838001178555610359565b82800160010185558215610359579182015b8281111561035857825182559160200191906001019061033d565b5b509050610366919061036a565b5090565b61038c91905b80821115610388576000816000905550600101610370565b5090565b9056fea165627a7a72305820ae6ca683d45ee8a71bba45caee29e4815147cd308f772c853a20dfe08214dbb50029""" # noqa: E501 + contract = w3.eth.contract(abi=abi, bytecode=bytecode) + tx_hash = contract.constructor().transact({ + 'from': deploy_address, + 'gas': 2000000 + }) + tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash, 180) + return w3.eth.contract(abi=abi, address=tx_receipt.contractAddress) + + +def test_hello_world(deploy_contract): + contract_object = deploy_contract + hw = contract_object.functions.bar().call() + logging.info(hw) + assert hw == "hello world" + + +def test_set_bar(eth_tester, w3, deploy_contract): + contract_object = deploy_contract + account1 = str(eth_tester.get_accounts()[1]) + event_filter = contract_object.events.barred().createFilter( + fromBlock='latest') # argument_filters={'args': '_bar'}) + + assert eth_tester.get_balance(account1) == 1000000000000000000000000 + + tx_hash = contract_object.functions.setBar( + "ethereum is the best").transact({ + 'from': account1, + 'gas': 2000000 + }) + w3.eth.waitForTransactionReceipt(tx_hash, 180) + + hw = contract_object.functions.bar().call() + assert hw == "ethereum is the best" + assert eth_tester.get_balance( + account1 + ) != 1000000000000000000000000 # gotta spend money on tx fees to call setBar + + event_list = event_filter.get_new_entries() + assert len(event_list) == 1 + event = event_list[0] + print(event) + assert event['args']['_bar'] == "ethereum is the best"