Skip to main content

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.
_20
pub fun setup() {
_20
}
_20
_20
// Test functions start with the 'test' prefix.
_20
pub fun testSomething() {
_20
}
_20
_20
pub fun testAnotherThing() {
_20
}
_20
_20
pub 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.
_20
pub fun tearDown() {
_20
}

Test Standard Library

The testing framework can be used by importing the built-in Test contract:


_10
import Test

Assertion

assert


_10
fun 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


_10
fun 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.


_10
fun expect(_ value: AnyStruct, _ matcher: Matcher)

Matchers

A matcher is an object that consists of a test function and associated utility functionality.


_26
pub struct Matcher {
_26
_26
pub let test: ((AnyStruct): Bool)
_26
_26
pub init(test: ((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
pub 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
pub 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.


_10
fun newMatcher<T: AnyStruct>(_ test: ((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:


_10
let isNegative = Test.newMatcher(fun (_ value: Int): Bool {
_10
return value < 0
_10
})
_10
_10
// Use `expect` function to test a value against the matcher.
_10
Test.expect(-15, isNegative)

Built-in matcher functions

The Test contract provides some built-in matcher functions for convenience.

  • fun equal(_ value: AnyStruct): Matcher

    Returns a matcher that succeeds if the tested value is equal to the given value. Accepts an AnyStruct value.

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
///
_87
pub struct Blockchain {
_87
_87
pub 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
pub 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
pub fun createAccount(): Account {
_87
return self.backend.createAccount()
_87
}
_87
_87
/// Add a transaction to the current block.
_87
///
_87
pub 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
pub 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
pub fun commitBlock() {
_87
self.backend.commitBlock()
_87
}
_87
_87
/// Executes a given transaction and commit the current block.
_87
///
_87
pub 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 commit the current block.
_87
///
_87
pub 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 initilizes it with the arguments.
_87
///
_87
pub 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
///
_21
pub struct interface BlockchainBackend {
_21
_21
pub fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult
_21
_21
pub fun createAccount(): Account
_21
_21
pub fun addTransaction(_ tx: Transaction)
_21
_21
pub fun executeNextTransaction(): TransactionResult?
_21
_21
pub fun commitBlock()
_21
_21
pub 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.


_10
let 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.


_10
let acct = blockchain.createAccount()

The returned account consist of the address of the account, and a publicKey associated with it.


_11
/// Account represents info about the account created on the blockchain.
_11
///
_11
pub struct Account {
_11
pub let address: Address
_11
pub 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.


_10
let result = blockchain.executeScript("pub 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
///
_13
pub struct ScriptResult {
_13
pub let status: ResultStatus
_13
pub let returnValue: AnyStruct?
_13
pub 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
///
_15
pub struct Transaction {
_15
pub let code: String
_15
pub let authorizers: [Address]
_15
pub let signers: [Account]
_15
pub 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.


_10
let 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


    _10
    let 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
    _10
    blockchain.addTransaction(tx)
    _10
    _10
    // Execute the next transaction in the block
    _10
    let 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
///
_11
pub struct TransactionResult {
_11
pub let status: ResultStatus
_11
pub 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.


_10
blockchain.commitBlock()

Deploying contracts

A contract can be deployed using the deployContract function of the Blockchain.


_10
let contractCode = "pub contract Foo{ pub let msg: String; init(_ msg: String){ self.msg = msg } pub fun sayHello(): String { return self.msg } }"
_10
_10
let 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 a such 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
///
_10
pub struct Configuration {
_10
pub 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 strings.


_10
import FooContract from "FooContract"
_10
import BarContract from "BarContract"
_10
_10
pub fun main() {
_10
// do something
_10
}

Then, before executing the script, the address mapping can be specified as follows:


_20
pub var blockchain = Test.newEmulatorBlockchain()
_20
pub var accounts: [Test.Account] = []
_20
_20
pub 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
//
_10
pub struct Error {
_10
pub 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).


_10
let err: Error? = ...
_10
_10
if 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.


_10
let contractCode = Test.readFile("./sample/contracts/FooContract.cdc")

readFile returns the content of the file as a string.