Cadence Testing Framework
The Cadence testing framework provides a convenient way to write tests for Cadence programs in Cadence.
This functionality is provided by the built-in Test contract.
The testing framework can only be used off-chain, e.g. by using the Flow CLI.
Tests must be written in the form of a Cadence script.
A test script may contain testing functions that starts with the test prefix,
a setup function that will always run before the tests,
and a tearDown function that will always run at the end of all test cases.
Both setup and tearDown functions are optional.
_20// A `setup` function that will always run before the rest of the methods._20// Can be used to initialize things that would be used across the test cases._20// e.g: initialling a blockchain backend, initializing a contract, etc._20access(all) fun setup() {_20}_20_20// Test functions start with the 'test' prefix._20access(all) fun testSomething() {_20}_20_20access(all) fun testAnotherThing() {_20}_20_20access(all) fun testMoreThings() {_20}_20_20// A `tearDown` function that will always run at the end of all test cases._20// e.g: Can be used to stop the blockchain back-end used for tests, etc. or any cleanup._20access(all) fun tearDown() {_20}
Test Standard Library
The testing framework can be used by importing the built-in Test contract:
_10import Test
Assertion
assert
_10fun assert(_ condition: Bool, message: String)
Fails a test-case if the given condition is false, and reports a message which explains how the condition is false.
The message argument is optional.
fail
_10fun fail(message: String)
Immediately fails a test-case, with a message explaining the reason to fail the test.
The message argument is optional.
expect
The expect function tests a value against a matcher (see matchers section), and fails the test if it's not a match.
_10fun expect(_ value: AnyStruct, _ matcher: Matcher)
Matchers
A matcher is an object that consists of a test function and associated utility functionality.
_26access(all) struct Matcher {_26_26    access(all) let test: fun(AnyStruct): Bool_26_26    access(all) init(test: fun(AnyStruct): Bool) {_26        self.test = test_26    }_26_26    /// Combine this matcher with the given matcher._26    /// Returns a new matcher that succeeds if this and the given matcher succeed._26    ///_26    access(all) fun and(_ other: Matcher): Matcher {_26        return Matcher(test: fun (value: AnyStruct): Bool {_26            return self.test(value) && other.test(value)_26        })_26    }_26_26    /// Combine this matcher with the given matcher._26    /// Returns a new matcher that succeeds if this or the given matcher succeeds._26    ///_26    access(all) fun or(_ other: Matcher): Matcher {_26        return Matcher(test: fun (value: AnyStruct): Bool {_26            return self.test(value) || other.test(value)_26        })_26    }_26}
The test function defines the evaluation criteria for a value, and returns a boolean indicating whether the value
conforms to the test criteria defined in the function.
The and and or functions can be used to combine this matcher with another matcher to produce a new matcher with
multiple testing criteria.
The and method returns a new matcher that succeeds if both this and the given matcher are succeeded.
The or method returns a new matcher that succeeds if at-least this or the given matcher is succeeded.
A matcher that accepts a generic-typed test function can be constructed using the newMatcher function.
_10fun newMatcher<T: AnyStruct>(_ test: fun(T): Bool): Test.Matcher
The type parameter T is bound to AnyStruct type. It is also optional.
For example, a matcher that checks whether a given integer value is negative can be defined as follows:
_10let isNegative = Test.newMatcher(fun (_ value: Int): Bool {_10    return value < 0_10})_10_10// Use `expect` function to test a value against the matcher._10Test.expect(-15, isNegative)
Built-in matcher functions
The Test contract provides some built-in matcher functions for convenience.
- 
fun equal(_ value: AnyStruct): MatcherReturns a matcher that succeeds if the tested value is equal to the given value. Accepts an AnyStructvalue.
Blockchain
A blockchain is an environment to which transactions can be submitted to, and against which scripts can be run. It imitates the behavior of a real network, for testing.
_87/// Blockchain emulates a real network._87///_87access(all) struct Blockchain {_87_87    access(all) let backend: AnyStruct{BlockchainBackend}_87_87    init(backend: AnyStruct{BlockchainBackend}) {_87        self.backend = backend_87    }_87_87    /// Executes a script and returns the script return value and the status._87    /// `returnValue` field of the result will be `nil` if the script failed._87    ///_87    access(all) fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult {_87        return self.backend.executeScript(script, arguments)_87    }_87_87    /// Creates a signer account by submitting an account creation transaction._87    /// The transaction is paid by the service account._87    /// The returned account can be used to sign and authorize transactions._87    ///_87    access(all) fun createAccount(): Account {_87        return self.backend.createAccount()_87    }_87_87    /// Add a transaction to the current block._87    ///_87    access(all) fun addTransaction(_ tx: Transaction) {_87        self.backend.addTransaction(tx)_87    }_87_87    /// Executes the next transaction in the block, if any._87    /// Returns the result of the transaction, or nil if no transaction was scheduled._87    ///_87    access(all) fun executeNextTransaction(): TransactionResult? {_87        return self.backend.executeNextTransaction()_87    }_87_87    /// Commit the current block._87    /// Committing will fail if there are un-executed transactions in the block._87    ///_87    access(all) fun commitBlock() {_87        self.backend.commitBlock()_87    }_87_87    /// Executes a given transaction and commits the current block._87    ///_87    access(all) fun executeTransaction(_ tx: Transaction): TransactionResult {_87        self.addTransaction(tx)_87        let txResult = self.executeNextTransaction()!_87        self.commitBlock()_87        return txResult_87    }_87_87    /// Executes a given set of transactions and commits the current block._87    ///_87    access(all) fun executeTransactions(_ transactions: [Transaction]): [TransactionResult] {_87        for tx in transactions {_87            self.addTransaction(tx)_87        }_87_87        let results: [TransactionResult] = []_87        for tx in transactions {_87            let txResult = self.executeNextTransaction()!_87            results.append(txResult)_87        }_87_87        self.commitBlock()_87        return results_87    }_87_87    /// Deploys a given contract, and initializes it with the arguments._87    ///_87    access(all) fun deployContract(_87        name: String,_87        code: String,_87        account: Account,_87        arguments: [AnyStruct]_87    ): Error? {_87        return self.backend.deployContract(_87            name: name,_87            code: code,_87            account: account,_87            arguments: arguments_87        )_87    }_87}
The BlockchainBackend provides the actual functionality of the blockchain.
_21/// BlockchainBackend is the interface to be implemented by the backend providers._21///_21access(all) struct interface BlockchainBackend {_21_21    access(all) fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult_21_21    access(all) fun createAccount(): Account_21_21    access(all) fun addTransaction(_ tx: Transaction)_21_21    access(all) fun executeNextTransaction(): TransactionResult?_21_21    access(all) fun commitBlock()_21_21    access(all) fun deployContract(_21        name: String,_21        code: String,_21        account: Account,_21        arguments: [AnyStruct]_21    ): Error?_21}
Creating a blockchain
A new blockchain instance can be created using the newEmulatorBlockchain method.
It returns a Blockchain which is backed by a new Flow Emulator instance.
_10let blockchain = Test.newEmulatorBlockchain()
Creating accounts
It may be necessary to create accounts during tests for various reasons, such as for deploying contracts, signing transactions, etc.
An account can be created using the createAccount function.
_10let acct = blockchain.createAccount()
The returned account consists of the address of the account, and a publicKey associated with it.
_11/// Account represents info about the account created on the blockchain._11///_11access(all) struct Account {_11    access(all) let address: Address_11    access(all) let publicKey: PublicKey_11_11    init(address: Address, publicKey: PublicKey) {_11        self.address = address_11        self.publicKey = publicKey_11    }_11}
Executing scripts
Scripts can be run with the executeScript function, which returns a ScriptResult.
The function takes script-code as the first argument, and the script-arguments as an array as the second argument.
_10let result = blockchain.executeScript("access(all) fun main(a: String) {}", ["hello"])
The script result consists of the status of the script execution, and a returnValue if the script execution was
successful, or an error otherwise (see errors section for more details on errors).
_13/// The result of a script execution._13///_13access(all) struct ScriptResult {_13    access(all) let status: ResultStatus_13    access(all) let returnValue: AnyStruct?_13    access(all) let error: Error?_13_13    init(status: ResultStatus, returnValue: AnyStruct?, error: Error?) {_13        self.status = status_13        self.returnValue = returnValue_13        self.error = error_13    }_13}
Executing transactions
A transaction must be created with the transaction code, a list of authorizes, a list of signers that would sign the transaction, and the transaction arguments.
_15/// Transaction that can be submitted and executed on the blockchain._15///_15access(all) struct Transaction {_15    access(all) let code: String_15    access(all) let authorizers: [Address]_15    access(all) let signers: [Account]_15    access(all) let arguments: [AnyStruct]_15_15    init(code: String, authorizers: [Address], signers: [Account], arguments: [AnyStruct]) {_15        self.code = code_15        self.authorizers = authorizers_15        self.signers = signers_15        self.arguments = arguments_15    }_15}
The number of authorizers must match the number of AuthAccount arguments in the prepare block of the transaction.
_10let tx = Test.Transaction(_10    code: "transaction { prepare(acct: AuthAccount) {} execute{} }",_10    authorizers: [account.address],_10    signers: [account],_10    arguments: [],_10)
There are two ways to execute the created transaction.
- 
Executing the transaction immediately _10let result = blockchain.executeTransaction(tx)This may fail if the current block contains transactions that have not being executed yet. 
- 
Adding the transaction to the current block, and executing it later. _10// Add to the current block_10blockchain.addTransaction(tx)_10_10// Execute the next transaction in the block_10let result = blockchain.executeNextTransaction()
The result of a transaction consists of the status of the execution, and an Error if the transaction failed.
_11/// The result of a transaction execution._11///_11access(all) struct TransactionResult {_11    access(all) let status: ResultStatus_11    access(all) let error: Error?_11_11    init(status: ResultStatus, error: Error) {_11        self.status = status_11        self.error = error_11    }_11 }
Commit block
commitBlock block will commit the current block, and will fail if there are any un-executed transactions in the block.
_10blockchain.commitBlock()
Deploying contracts
A contract can be deployed using the deployContract function of the Blockchain.
_10let contractCode = "access(all) contract Foo{ access(all) let msg: String;   init(_ msg: String){ self.msg = msg }   access(all) fun sayHello(): String { return self.msg } }"_10_10let err = blockchain.deployContract(_10    name: "Foo",_10    code: contractCode,_10    account: account,_10    arguments: ["hello from args"],_10)
An Error is returned if the contract deployment fails. Otherwise, a nil is returned.
Configuring import addresses
A common pattern in Cadence projects is to define the imports as file locations and specify the addresses corresponding to each network in the Flow CLI configuration file. When writing tests for such a project, it may also require to specify the addresses to be used during the tests as well. However, during tests, since accounts are created dynamically and the addresses are also generated dynamically, specifying the addresses statically in a configuration file is not an option.
Hence, the test framework provides a way to specify the addresses using the
useConfiguration(_ configuration: Test.Configuration) function in Blockchain.
The Configuration struct consists of a mapping of import locations to their addresses.
_10/// Configuration to be used by the blockchain._10/// Can be used to set the address mapping._10///_10access(all) struct Configuration {_10    access(all) let addresses: {String: Address}_10_10    init(addresses: {String: Address}) {_10        self.addresses = addresses_10    }_10}
The Blockchain.useConfiguration is a run-time alternative for
statically defining contract addresses in the flow.json config file.
The configurations can be specified during the test setup as a best-practice.
e.g: Assume running a script that imports FooContract and BarContract.
The import locations for the two contracts can be specified using the two placeholders "FooContract" and
"BarContract". These placeholders can be any unique string.
_10import FooContract from "FooContract"_10import BarContract from "BarContract"_10_10access(all) fun main() {_10    // do something_10}
Then, before executing the script, the address mapping can be specified as follows:
_20access(all) var blockchain = Test.newEmulatorBlockchain()_20access(all) var accounts: [Test.Account] = []_20_20access(all) fun setup() {_20    // Create accounts in the blockchain._20_20    let acct1 = blockchain.createAccount()_20    accounts.append(acct1)_20_20    let acct2 = blockchain.createAccount()_20    accounts.append(acct2)_20_20    // Set the configuration with the addresses._20    // They keys of the mapping should be the placeholders used in the imports._20_20    blockchain.useConfiguration(Test.Configuration({_20        "FooContract": acct1.address,_20        "BarContract": acct2.address_20    }))_20}
The subsequent operations on the blockchain (e.g: contract deployment, script/transaction execution) will resolve the import locations to the provided addresses.
Errors
An Error maybe returned when an operation (such as executing a script, executing a transaction, etc.) is failed.
Contains a message indicating why the operation failed.
_10// Error is returned if something has gone wrong._10//_10access(all) struct Error {_10    access(all) let message: String_10_10    init(_ message: String) {_10        self.message = message_10    }_10}
An Error may typically be handled by failing the test case or by panicking (which will result in failing the test).
_10let err: Error? = ..._10_10if let err = err {_10    panic(err.message)_10}
Reading from files
Writing tests often require constructing source-code of contracts/transactions/scripts in the test script. Testing framework provides a convenient way to load programs from a local file, without having to manually construct them within the test script.
_10let contractCode = Test.readFile("./sample/contracts/FooContract.cdc")
readFile returns the content of the file as a string.
Examples
This repository contains many functional examples that demonstrate most of the above features, both for contrived and real-world smart contracts. It also contains a detailed explanation on using code coverage for Cadence.