This repo contains tools and libraries for automating Neo smart contract tests.
This library several types needed for automated testing of Neo smart contracts.
TestApplicationEngine
- Subclass of NeoApplicationEngine
that can run independently of a running Neo blockchain. Heavily based off the Smart Contract Debugger'sDebugApplicationEngine
.CheckpointFixture
- class to manage the use of a Neo-Express checkpoint as a shared resource for multiple tests. Designed to work with xUnit's Class Fixture feature but can be used with other test frameworks if desiredExtensions
- a set of extension methods used to make it easier to interact with and generate execution scripts for smart contracts.GetContract<T>
- retrieve theContractState
value for a given contract typeGetContractStorages<T>
- retrieve the storage for a contract as a .NETIReadOnlyDictionary<ReadOnlyMemory[byte], StorageItem>
for easy assertionCreate/Load/ExecuteScript<T>
- generate contract execution script from strongly typed LINQ expression and optionally load/execute it
Example:
// metadata used by CheckpointFixture to locate checkpoint file
[CheckpointPath("checkpoints/contract-deployed.nxp3-checkpoint")]
public class ContractDeployedTests : IClassFixture<CheckpointFixture<ContractDeployedTests>>
{
readonly CheckpointFixture fixture;
// xUnit.NET provides CheckpointFixture instance via constructor injection
public ContractDeployedTests(CheckpointFixture<ContractDeployedTests> fixture)
{
this.fixture = fixture;
}
[Fact]
public void Can_register_domain()
{
// each test in the class retrieves a fresh IStore instance from the fixture
using var store = fixture.GetCheckpointStore();
using var snapshot = new SnapshotView(store);
// Use GetContractStorages to ensure storage is empty
snapshot.GetContractStorages<Registrar>().Any().Should().BeFalse();
// ExecuteScript converts the provided expression(s) into a Neo script
// loads them into the engine and executes it
using var engine = new TestApplicationEngine(snapshot, ALICE);
engine.ExecuteScript<Registrar>(c => c.register(DOMAIN_NAME, ALICE));
// Assert test step omitted
}
This library builds on the Fluent Assertions library and provides custom assertions for Neo VM StackItems, Neo StorageItems and NotifyEventArgs.
Examples:
[Fact]
public void Can_register_domain()
{
// Test arrange and act steps omitted
engine.State.Should().Be(VMState.HALT);
engine.ResultStack.Should().HaveCount(1);
// Assert StackItem as boolean
engine.ResultStack.Peek(0).Should().BeTrue();
// retrieve storages for a given contract as a IReadOnlyDictionary
var storages = snapshot.GetContractStorages<Registrar>();
byte[] DOMAIN_NAME = Neo.Utility.StrictUTF8.GetBytes("sample.domain");
storages.TryGetValue(DOMAIN_NAME, out var item).Should().BeTrue();
// Assert StorageItem as a Uint160 value
UInt160 ALICE = "NhGxW6BtLRhFLqh2oWqeRpNj8aNzKybRoV".FromAddress();
item!.Should().Be(ALICE);
// Assert Notifications
engine.Notifications.Should().HaveCount(1);
engine.Notifications[0].Should()
.BeSentBy(snapshot.GetContract<Registrar.Events>())
.And
// use LINQ expression to specify strongly-typed exepcted notification values
.BeEquivalentTo<Registrar.Events>(c => c.Register("sample.domain", ALICE));
}
While the Neo Test Harness and Assertions libraries described above are great, they are only available to C# Neo dApp developers. Developers building their contracts using other languages like Python, Java or Go can't use them. The Neo Test Runner is a stand-alone EXE, shipped as a .NET Tool that can execute test scripts against an emulated Neo blockchain environment and writes the results to the console in an easy-to-parse JSON format.
To install the test runner, use the dotnet tool install
command:
> dotnet tool install Neo.Test.Runner -g
Example Command Execution:
> neo-test-runner register-sample-domain.neo-invoke.json --account bob --checkpoint contract-deployed.neoxp-checkpoint --express default.neo-express --storages registrar --nef-file src/bin/sc/registrar.nef
Example Output:
{
"state": "HALT",
"exception": null,
"gasconsumed": "0.0869496",
"logs": [],
"notifications": [],
"stack": [
{
"type": "Boolean",
"value": true
}
],
"storages": [
{
"name": "registrar",
"hash": "0xc6cf77e89ff11499717c7f2c83416e2c0273b2d6",
"values": [
{
"key": "UmVnaXN0cmFy",
"value": "iAKhsjONfqImOHWgs72sXPvwO4U="
},
{
"key": "ZG9tYWluT3duZXJzc2FtcGxlLmRvbWFpbg==",
"value": "o2rCWQljs56nfcn8JzEaqt4Ql08="
}
]
}
],
"code-coverage": {
"contract-hash": "0xc6cf77e89ff11499717c7f2c83416e2c0273b2d6",
"debug-info-hash": "0xf69e5188632deb3a9273519efc86cb68da8d42b8",
"hit-map": {
"3": 0,
"4": 0,
"53": 0,
"59": 0,
"63": 1,
"215": 1,
"247": 1,
// emits the hit count for every statement in the contract
},
"branch-map": {
"14": "0-0",
"63": "0-1",
"258": "1-0",
// emits the hit count for each branch in the contract
}
}
}
For C# developers, the Neo.BuildTasks
package includes multiple MSBuild tasks to make
Neo smart contract development easier. These tasks include:
- NeoCsc - run
nccs
C# contract compiler - NeoExpressBatch - run
neoxp batch
- NeoContractInterface - generate a C# interface from contract manifest for use in tests
Note: both NeoCsc and NeoExpressBatch tasks assume the associated .NET tool is installed either globally or locally. If installed both globally and locally, the locally installed version will be used.
These tasks can be enabled simply by adding a PackageReference with PrivateAssets="All"
then setting MSBuild properties and/or items.
- To enable NeoCsc task, set
<NeoContractName>
property to the name you want the contract to have. Typically, this is set to$(AssemblyName)
. - To enable NeoExpressBatch task, set
<NeoExpressBatchFile>
property to the path of the NeoExpress batch file you want to execute - To enable NeoContractInterface task, set a
<NeoContractReference>
item in your test project with a path to the contract project.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NeoContractName>$(AssemblyName)</NeoContractName>
<NeoExpressBatchFile>..\express.batch</NeoExpressBatchFile>
<Nullable>enable</Nullable>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Neo.SmartContract.Framework" Version="3.3.1" />
<PackageReference Include="Neo.BuildTasks" Version="3.3.10-preview" PrivateAssets="All" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<NeoContractReference Include="..\src\registrar.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Neo.Assertions" Version="3.3.10-preview" />
<PackageReference Include="Neo.BuildTasks" Version="3.3.10-preview" PrivateAssets="All" />
<PackageReference Include="Neo.Test.Harness" Version="3.3.10-preview" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="All" />
</ItemGroup>
</Project>
// CSharp
[NeoTestHarness.Contract("Neo.Example.Nep17Token")]
interface Nep17Token
{
System.Numerics.BigInteger balanceOf(Neo.UInt160 account);
System.Numerics.BigInteger decimals();
void deploy(bool update);
void destroy();
void disablePayment();
void enablePayment();
void onPayment(Neo.UInt160 from, System.Numerics.BigInteger amount, object? data);
string symbol();
System.Numerics.BigInteger totalSupply();
bool transfer(Neo.UInt160 from, Neo.UInt160 to, System.Numerics.BigInteger amount, object? data);
void update(byte[] nefFile, string manifest);
bool verify();
interface Events
{
void Transfer(Neo.UInt160 arg1, Neo.UInt160 arg2, System.Numerics.BigInteger arg3);
}
}
For C# developers, the Neo.Collector
package can be used to collect smart contract code coverage.
By specifying "Neo code coverage" as the collector for a dotnet test
run, the developer can get
coverage information for the contracts in the popular Cobertura format.
For more details, see the code coverage documentation