Skip to content

Commit

Permalink
CU-86dtu8tcn - Add boa test constructor to the documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
luc10921 committed Jun 19, 2024
1 parent c9d1693 commit 8177c9f
Showing 1 changed file with 41 additions and 25 deletions.
66 changes: 41 additions & 25 deletions docs/source/testing-and-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,24 @@ Boa3.compile_and_save('path/to/your/file.py', debug=True)

### Downloading

Install [boa-test-constructor](https://pypi.org/project/boa-test-constructor/) with pip. We use this extension to run an isolated
test environment for smart contracts with a neo-go node. When installing boa-test-constructor, [neo-mamba](https://dojo.coz.io/neo3/mamba/index.html)
will be installed too.
Install [boa-test-constructor](https://pypi.org/project/boa-test-constructor/) with pip by running:
```shell
$ pip install neo3-boa[test]
```
This will ensure that the boa-test-constructor version that will be installed is compatible with the latest version of
neo3-boa.

We use this extension to run an isolated test environment for smart contracts with a neo-go node. When installing
boa-test-constructor, [neo-mamba](https://dojo.coz.io/neo3/mamba/index.html) will be installed too.

### Testing

Create a Python Script, import the `SmartContractTestCase` class, and create a test class that inherits it. To set up
the test environment, you'll need to override the `setUpClass` method from `SmartContractTestCase`. This method is
synchronous, so if you need to set up asynchronous tasks, you can create another async method and use it int the
`asyncio.run` method from `asyncio`. Common operations would be: creating accounts, deploying the smart contract,
selecting your "main" smart contract, and transferring GAS to the new accounts.

Then, create functions to test the expected behavior of your smart contract. To invoke or test invoke your smart
contract, use the `call` method from `SmartContractTestCase`. The two positional parameters are the name of the method
you want to invoke and a list of its arguments. The keyword parameters are the return type, a list of signing accounts,
a list of signers, and the smart contract you want to invoke. If you get an error when calling a smart contract, then an
error will be raised.

To persist an invocation, use the `signing_accounts` parameter to pass a list of signing accounts when calling the
smart contract. If you don't pass it, then it will always be a test invoke. The `signers` parameter can be used
alongside the `signing_accounts` if you want to change the witness scope of the invocation, or by itself if you want to
test invoke but also define the signers of the transaction.

Your Python Script should look something like this:
synchronous, so if you need to set up asynchronous tasks, like tasks that need to interact with the local blockchain,
then you can create another async method and use it int the `asyncio.run` method from `asyncio`. Common operations would
be: creating accounts, deploying the smart contract, selecting your "main" smart contract, and transferring GAS to the
new accounts.

```python
import asyncio
Expand All @@ -81,18 +75,21 @@ from neo3.contracts.contract import CONTRACT_HASHES

GAS = CONTRACT_HASHES.GAS_TOKEN

class SmartContractTest(SmartContractTestCase):
# the smart contract that will be tested is hello_world_with_deploy.py from the "Neo Methods" https://dojo.coz.io/neo3/boa/getting-started.html#neo-methods
class HelloWorldWithDeployTest(SmartContractTestCase):
genesis: Account
user1: Account

# if this variable is set, then this contract hash will be called whenever you don't use the `target_contract` parameter on the `call` method
# if this variable is set, then this contract hash will be used whenever you don't specify which smart contract you'll want to invoke
contract_hash: types.UInt160

@classmethod
def setUpClass(cls) -> None:
# whenever a new test is run, the local blockchain will be reset, that's why we need to set up the environment again
super().setUpClass()
# you can name the account whatever you want, but the password needs to be "123"
cls.user1 = cls.node.wallet.account_new("123", "alice")
# this is a boa-test-constructor deliberate decision to make the tests run faster
cls.user1 = cls.node.wallet.account_new(label="alice", password="123")
cls.genesis = cls.node.wallet.account_get_by_label("committee")

asyncio.run(cls.asyncSetupClass())
Expand All @@ -101,15 +98,34 @@ class SmartContractTest(SmartContractTestCase):
async def asyncSetupClass(cls) -> None:
# this `transfer` method already uses the correct amount of decimals for the token
await cls.transfer(GAS, cls.genesis.script_hash, cls.user1.script_hash, 100)

# the smart contract I'm deploying is hello_world_with_deploy.py from the "Neo Methods" https://dojo.coz.io/neo3/boa/getting-started.html#neo-methods
cls.contract_hash = await cls.deploy("./smart_contract.nef", cls.genesis)

cls.contract_hash = await cls.deploy("./hello_world_with_deploy.nef", cls.genesis)
```

Then, create functions to test the expected behavior of your smart contract. To invoke your smart contract, use the
`call` method from `SmartContractTestCase`. The two positional parameters are the name of the method you want to invoke
and a list of its arguments. The keyword parameters are the return type, a list of signing accounts, a list of signers,
and the smart contract you want to invoke. Method name, and return type are obligatory, but you'll most likely also need
to pass the args too. If you get an error when calling a smart contract, then an error will be raised.

```python
# inside the HelloWorldWithDeployTest class
async def test_message(self):
expected = "Hello World"
result, _ = await self.call("get_message", return_type=str)
self.assertEqual(expected, result)
```

To persist an invocation, use the `signing_accounts` parameter to pass a list of signing accounts when calling the
smart contract. If you don't pass it, then it will always be a test invoke, meaning it won't be saved on the local
blockchain. The `signers` parameter can be used alongside the `signing_accounts` if you want to change the
[witness scope](https://developers.neo.org/docs/n3/foundation/Transactions#signature-scope) of the invocation, or by
itself if you want to test invoke but also define the [signers](https://developers.neo.org/docs/n3/foundation/Transactions#signers)
of the transaction.

```python
# continuation of the 'async def test_message(self)' function
# to set this message in the smart contract, we need to pass the signing account
new_message = "New Message"
# since we want this change to persist, we need to pass the signing account
result, _ = await self.call("set_message", [new_message], return_type=None,
Expand Down

0 comments on commit 8177c9f

Please sign in to comment.