Cadence Testing Framework
Write Cadence unit tests using the built-in Test contract — assertions, matchers, blockchain emulation, transactions, scripts, events, coverage, and the `flow test` workflow.
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 offchain (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 always runs before the tests, a tearDown function that always runs at the end of all test cases, a beforeEach function that runs before each test case, and an afterEach function that runs after each test case. All of the above four functions are optional.
// A `setup` function that always runs before the rest of the test cases.
// Can be used to initialize things that would be used across the test cases.
// e.g: initialling a blockchain backend, initializing a contract, etc.
access(all)
fun setup() {
}
// The `beforeEach` function runs before each test case. Can be used to perform
// some state cleanup before each test case, among other things.
access(all)
fun beforeEach() {
}
// The `afterEach` function runs after each test case. Can be used to perform
// some state cleanup after each test case, among other things.
access(all)
fun afterEach() {
}
// Valid test functions start with the 'test' prefix.
access(all)
fun testSomething() {
}
access(all)
fun testAnotherThing() {
}
access(all)
fun testMoreThings() {
}
// Test functions cannot have any arguments or return values.
access(all)
fun testInvalidSignature(message: String): Bool {
}
// A `tearDown` function that always runs at the end of all test cases.
// e.g: Can be used to stop the blockchain back-end used for tests, etc. or any cleanup.
access(all)
fun tearDown() {
}Test standard library
The testing framework can be used by importing the built-in Test contract:
import TestAssertions
Test.assert
view fun assert(_ condition: Bool, message: String)Fails a test case if the given condition is false, and reports a message that explains why the condition is false.
The message argument is optional.
import Test
access(all)
fun testExample() {
Test.assert(2 == 2)
Test.assert([1, 2, 3].length == 0, message: "Array length is not 0")
}Test.fail
view fun fail(message: String)Immediately fails a test case, with a message explaining the reason to fail the test.
The message argument is optional.
import Test
access(all)
fun testExample() {
let array = [1, 2, 3]
if array.length != 0 {
Test.fail(message: "Array length is not 0")
}
}Test.expect
fun expect(_ value: AnyStruct, _ matcher: Matcher)The expect function tests a value against a matcher (see matchers section), and fails the test if it's not a match.
import Test
access(all)
fun testExample() {
let array = [1, 2, 3]
Test.expect(array.length, Test.equal(3))
}Test.assertEqual
fun assertEqual(_ expected: AnyStruct, _ actual: AnyStruct)The assertEqual function fails the test case if the given values are not equal, and reports a message that explains how the two values differ.
import Test
access(all)
struct Foo {
access(all)
let answer: Int
init(answer: Int) {
self.answer = answer
}
}
access(all)
fun testExample() {
Test.assertEqual("this string", "this string")
Test.assertEqual(21, 21)
Test.assertEqual(true, true)
Test.assertEqual([1, 2, 3], [1, 2, 3])
Test.assertEqual(
{1: true, 2: false, 3: true},
{1: true, 2: false, 3: true}
)
let address1 = Address(0xf8d6e0586b0a20c7)
let address2 = Address(0xf8d6e0586b0a20c7)
Test.assertEqual(address1, address2)
let foo1 = Foo(answer: 42)
let foo2 = Foo(answer: 42)
Test.assertEqual(foo1, foo2)
let number1: Int64 = 100
let number2: UInt64 = 100
// Note that the two values need to have exactly the same type,
// and not just value, otherwise the assertion fails:
// assertion failed: not equal: expected: 100, actual: 100
Test.assertEqual(number1, number2)
}Test.expectFailure
fun expectFailure(_ functionWrapper: ((): Void), errorMessageSubstring: String)The expectFailure function wraps a function call in a closure and expects it to fail with an error message that contains the given error message portion.
import Test
access(all)
struct Foo {
access(self)
let answer: UInt8
init(answer: UInt8) {
self.answer = answer
}
access(all)
fun correctAnswer(_ input: UInt8): Bool {
if self.answer != input {
panic("wrong answer!")
}
return true
}
}
access(all)
fun testExample() {
let foo = Foo(answer: 42)
Test.expectFailure(fun(): Void {
foo.correctAnswer(43)
}, errorMessageSubstring: "wrong answer!")
}Matchers
A matcher is an object that consists of a test function and associated utility functionality.
access(all)
struct Matcher {
access(all)
let test: fun(AnyStruct): Bool
access(all)
init(test: fun(AnyStruct): Bool) {
self.test = test
}
/// Combine this matcher with the given matcher.
/// Returns a new matcher that succeeds if this and the given matcher succeed.
///
access(all)
fun and(_ other: Matcher): Matcher {
return Matcher(test: fun (value: AnyStruct): Bool {
return self.test(value) && other.test(value)
})
}
/// Combine this matcher with the given matcher.
/// Returns a new matcher that succeeds if this or the given matcher succeeds.
///
access(all)
fun or(_ other: Matcher): Matcher {
return Matcher(test: fun (value: AnyStruct): Bool {
return self.test(value) || other.test(value)
})
}
}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.
view fun newMatcher<T: AnyStruct>(_ test: fun(T): Bool): Test.MatcherThe 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:
import Test
access(all)
fun testExample() {
let isNegative = Test.newMatcher(fun (_ value: Int): Bool {
return value < 0
})
Test.expect(-15, isNegative)
// Alternatively, we can use `Test.assert` and the matcher's `test` function.
Test.assert(isNegative.test(-15), message: "number is not negative")
}
access(all)
fun testCustomMatcherUntyped() {
let matcher = Test.newMatcher(fun (_ value: AnyStruct): Bool {
if !value.getType().isSubtype(of: Type<Int>()) {
return false
}
return (value as! Int) > 5
})
Test.expect(8, matcher)
}
access(all)
fun testCustomMatcherTyped() {
let matcher = Test.newMatcher<Int>(fun (_ value: Int): Bool {
return value == 7
})
Test.expect(7, matcher)
}The Test contract provides some built-in matcher functions for convenience.
Test.equal
view fun equal(_ value: AnyStruct): MatcherThe equal function returns a matcher that succeeds if the tested value is equal to the given value. Accepts an AnyStruct value.
import Test
access(all)
fun testExample() {
let array = [1, 2, 3]
Test.expect([1, 2, 3], Test.equal(array))
}Test.beGreaterThan
view fun beGreaterThan(_ value: Number): MatcherThe beGreaterThan function returns a matcher that succeeds if the tested value is a number and greater than the given number.
import Test
access(all)
fun testExample() {
let str = "Hello, there"
Test.expect(str.length, Test.beGreaterThan(5))
}Test.beLessThan
view fun beLessThan(_ value: Number): MatcherThe beLessThan function returns a matcher that succeeds if the tested value is a number and less than the given number.
import Test
access(all)
fun testExample() {
let str = "Hello, there"
Test.expect(str.length, Test.beLessThan(15))
}Test.beNil
view fun beNil(): MatcherThe beNil function returns a new matcher that checks if the given test value is nil.
import Test
access(all)
fun testExample() {
let message: String? = nil
Test.expect(message, Test.beNil())
}Test.beEmpty
view fun beEmpty(): MatcherThe beEmpty function returns a matcher that succeeds if the tested value is an array or dictionary and the tested value contains no elements.
import Test
access(all)
fun testExample() {
let array: [String] = []
Test.expect(array, Test.beEmpty())
let dictionary: {String: String} = {}
Test.expect(dictionary, Test.beEmpty())
}Test.haveElementCount
view fun haveElementCount(_ count: Int): MatcherThe haveElementCount function returns a matcher that succeeds if the tested value is an array or dictionary and has the given number of elements.
import Test
access(all)
fun testExample() {
let array: [String] = ["one", "two", "three"]
Test.expect(array, Test.haveElementCount(3))
let dictionary: {String: Int} = {"one": 1, "two": 2, "three": 3}
Test.expect(dictionary, Test.haveElementCount(3))
}Test.contain
view fun contain(_ element: AnyStruct): MatcherThe contain function returns a matcher that succeeds if the tested value is an array that contains a value that is equal to the given value, or the tested value is a dictionary that contains an entry where the key is equal to the given value.
access(all)
fun testExample() {
let array: [String] = ["one", "two", "three"]
Test.expect(array, Test.contain("one"))
let dictionary: {String: Int} = {"one": 1, "two": 2, "three": 3}
Test.expect(dictionary, Test.contain("two"))
}Test.beSucceeded
fun beSucceeded(): MatcherThe beSucceeded function returns a new matcher that checks if the given test value is either a ScriptResult or TransactionResult and the ResultStatus is succeeded. Returns false in any other case.
import Test
access(all)
fun testExample() {
let result = Test.executeScript(
"access(all) fun main(): Int { return 2 + 3 }",
[]
)
Test.expect(result, Test.beSucceeded())
Test.assertEqual(5, result.returnValue! as! Int)
}Test.beFailed
fun beFailed(): MatcherThe beFailed function returns a new matcher that checks if the given test value is either a ScriptResult or TransactionResult and the ResultStatus is failed. Returns false in any other case.
import Test
access(all)
fun testExample() {
let account = Test.createAccount()
let tx = Test.Transaction(
code: "transaction { execute{ panic(\"some error\") } }",
authorizers: [],
signers: [account],
arguments: [],
)
let result = Test.executeTransaction(tx)
Test.expect(result, Test.beFailed())
}Matcher combinators
The built-in matchers, as well as custom matchers, can be combined with the three available combinators:
notorand
This assures more elaborate matchers and increases re-usability.
not
fun not(_ matcher: Matcher): MatcherThe not function returns a new matcher that negates the test of the given matcher.
import Test
access(all)
fun testExample() {
let isEven = Test.newMatcher<Int>(fun (_ value: Int): Bool {
return value % 2 == 0
})
Test.expect(8, isEven)
Test.expect(7, Test.not(isEven))
let isNotEmpty = Test.not(Test.beEmpty())
Test.expect([1, 2, 3], isNotEmpty)
}or
fun or(_ other: Matcher): MatcherThe Matcher.or function combines this matcher with the given matcher. Returns a new matcher that succeeds if this or the given matcher succeed. If this matcher succeeds, then the other matcher would not be tested.
import Test
access(all)
fun testExample() {
let one = Test.equal(1)
let two = Test.equal(2)
let oneOrTwo = one.or(two)
Test.expect(2, oneOrTwo)
}and
fun and(_ other: Matcher): MatcherThe Matcher.and function combines this matcher with the given matcher. Returns a new matcher that succeeds if this and the given matcher succeed.
import Test
access(all)
fun testExample() {
let sevenOrMore = Test.newMatcher<Int>(fun (_ value: Int): Bool {
return value >= 7
})
let lessThanTen = Test.newMatcher<Int>(fun (_ value: Int): Bool {
return value <= 10
})
let betweenSevenAndTen = sevenOrMore.and(lessThanTen)
Test.expect(8, betweenSevenAndTen)
}Blockchain operations
The Test contract provides functions for submitting transactions, running scripts, and otherwise driving a blockchain environment that imitates the behavior of a real network. It is backed by a Flow Emulator instance, and no explicit setup is required — just import Test and call its functions directly.
The available functions include:
/// Executes a script and returns the script return value and the status.
/// `returnValue` field of the result will be `nil` if the script failed.
access(all)
fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult
/// Creates a signer account by submitting an account creation transaction.
/// The transaction is paid by the service account.
/// The returned account can be used to sign and authorize transactions.
access(all)
fun createAccount(): TestAccount
/// Returns the account for the given address.
access(all)
fun getAccount(_ address: Address): TestAccount
/// Add a transaction to the current block.
access(all)
fun addTransaction(_ tx: Transaction)
/// Executes the next transaction in the block, if any.
/// Returns the result of the transaction, or nil if no transaction was scheduled.
access(all)
fun executeNextTransaction(): TransactionResult?
/// Commit the current block.
/// Committing will fail if there are un-executed transactions in the block.
access(all)
fun commitBlock()
/// Executes a given transaction and commits the current block.
access(all)
fun executeTransaction(_ tx: Transaction): TransactionResult
/// Executes a given set of transactions and commits the current block.
access(all)
fun executeTransactions(_ transactions: [Transaction]): [TransactionResult]
/// Deploys a given contract, and initializes it with the arguments.
access(all)
fun deployContract(
name: String,
path: String,
arguments: [AnyStruct]
): Error?
/// Returns all the logs from the blockchain, up to the calling point.
access(all)
fun logs(): [String]
/// Returns the service account of the blockchain. Can be used to sign
/// transactions with this account.
access(all)
fun serviceAccount(): TestAccount
/// Returns all events emitted from the blockchain.
access(all)
fun events(): [AnyStruct]
/// Returns all events emitted from the blockchain, filtered by type.
access(all)
fun eventsOfType(_ type: Type): [AnyStruct]
/// Resets the state of the blockchain to the given height.
access(all)
fun reset(to height: UInt64)
/// Moves the time of the blockchain by the given delta,
/// which should be passed in the form of seconds.
access(all)
fun moveTime(by delta: Fix64)Creating accounts
It may be necessary to create accounts during tests for various reasons, such as for deploying contracts, signing transactions, and so on. An account can be created using the Test.createAccount function.
import Test
access(all)
let account = Test.createAccount()
access(all)
fun testExample() {
log(account.address)
}The following response is returned when running the above command from the command line:
flow test tests/test_sample_usage.cdc
3:31PM DBG LOG: 0x01cf0e2f2f715450
Test results: "tests/test_sample_usage.cdc"
- PASS: testExampleThe returned account consists of the address of the account and a publicKey associated with it.
/// TestAccount represents info about the account created on the blockchain.
///
access(all)
struct TestAccount {
access(all)
let address: Address
access(all)
let publicKey: PublicKey
init(address: Address, publicKey: PublicKey) {
self.address = address
self.publicKey = publicKey
}
}Executing scripts
Scripts can be run with the Test.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.
import Test
access(all)
fun testExample() {
let code = "access(all) fun main(name: String): String { return \"Hello, \".concat(name) }"
let args = ["Peter"]
let scriptResult = Test.executeScript(code, args)
// Assert that the script was successfully executed.
Test.expect(scriptResult, Test.beSucceeded())
// returnValue has always the type `AnyStruct`,
// so we need to type-cast accordingly.
let returnValue = scriptResult.returnValue! as! String
Test.assertEqual("Hello, Peter", returnValue)
}The script result consists of the status of the script execution, a returnValue if the script execution was successful, or an error otherwise (see errors section for more details on errors).
/// The result of a script execution.
///
access(all)
struct ScriptResult {
access(all)
let status: ResultStatus
access(all)
let returnValue: AnyStruct?
access(all)
let error: Error?
init(status: ResultStatus, returnValue: AnyStruct?, error: Error?) {
self.status = status
self.returnValue = returnValue
self.error = error
}
}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.
/// Transaction that can be submitted and executed on the blockchain.
///
access(all)
struct Transaction {
access(all)
let code: String
access(all)
let authorizers: [Address]
access(all)
let signers: [TestAccount]
access(all)
let arguments: [AnyStruct]
init(code: String, authorizers: [Address], signers: [TestAccount], arguments: [AnyStruct]) {
self.code = code
self.authorizers = authorizers
self.signers = signers
self.arguments = arguments
}
}The number of authorizers must match the number of &Account parameters in the prepare block of the transaction.
import Test
access(all)
let account = Test.createAccount()
// There are two ways to execute the created transaction.
access(all)
fun testExample() {
let tx = Test.Transaction(
code: "transaction { prepare(acct: &Account) {} execute{} }",
authorizers: [account.address],
signers: [account],
arguments: [],
)
// Executing the transaction immediately
// This may fail if the current block contains
// transactions that have not being executed yet.
let txResult = Test.executeTransaction(tx)
Test.expect(txResult, Test.beSucceeded())
}
access(all)
fun testExampleTwo() {
let tx = Test.Transaction(
code: "transaction { prepare(acct: &Account) {} execute{} }",
authorizers: [account.address],
signers: [account],
arguments: [],
)
// Add to the current block
Test.addTransaction(tx)
// Execute the next transaction in the block
let txResult = Test.executeNextTransaction()!
Test.expect(txResult, Test.beSucceeded())
}The result of a transaction consists of the status of the execution, and an Error if the transaction failed.
/// The result of a transaction execution.
///
access(all)
struct TransactionResult {
access(all)
let status: ResultStatus
access(all)
let error: Error?
init(status: ResultStatus, error: Error?) {
self.status = status
self.error = error
}
}Commit block
commitBlock block commits the current block and will fail if there are any unexecuted transactions in the block.
import Test
access(all)
let account = Test.createAccount()
access(all)
fun testExample() {
let tx = Test.Transaction(
code: "transaction { prepare(acct: &Account) {} execute{} }",
authorizers: [account.address],
signers: [account],
arguments: [],
)
Test.commitBlock()
Test.addTransaction(tx)
// This will fail with `error: internal error: pending block with ID 1f9...c0b7740d2 cannot be committed before execution`
Test.commitBlock()
}Deploying contracts
A contract can be deployed using the Test.deployContract function.
Suppose we have this contract (Foo.cdc):
access(all)
contract Foo {
access(all)
let msg: String
init(_ msg: String) {
self.msg = msg
}
access(all)
fun sayHello(): String {
return self.msg
}
}import Test
access(all)
fun testExample() {
let err = Test.deployContract(
name: "Foo",
path: "./Foo.cdc",
arguments: ["hello from args"],
)
Test.expect(err, Test.beNil())
}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 imports as file locations (e.g. import "Foo") and specify the addresses corresponding to each network in the Flow CLI configuration file. When writing tests for such a project, the same mechanism is used — import locations are resolved against the contracts section of flow.json, so no additional setup is required in the test script itself.
Suppose this script is saved in say_hello.cdc:
import "Foo"
access(all)
fun main(): String {
return Foo.sayHello()
}Once Foo is declared in flow.json and deployed with Test.deployContract, the import is resolved automatically:
import Test
access(all)
fun setup() {
let err = Test.deployContract(
name: "Foo",
path: "./Foo.cdc",
arguments: ["hello from args"],
)
Test.expect(err, Test.beNil())
}
access(all)
fun testExample() {
let script = Test.readFile("say_hello.cdc")
let scriptResult = Test.executeScript(script, [])
Test.expect(scriptResult, Test.beSucceeded())
let returnValue = scriptResult.returnValue! as! String
Test.assertEqual("hello from args", returnValue)
}Errors
An Error maybe returned when an operation (such as executing a script, executing a transaction, and so on) has failed. It contains a message indicating why the operation failed.
// Error is returned if something has gone wrong.
//
access(all)
struct Error {
access(all)
let message: String
init(_ message: String) {
self.message = message
}
}An Error can be asserted against its presence or absence.
import Test
access(all)
let account = Test.createAccount()
access(all)
fun testExample() {
let script = Test.readFile("say_hello.cdc")
let scriptResult = Test.executeScript(script, [])
// If we expect a script to fail, we can use Test.beFailed() instead
Test.expect(scriptResult, Test.beSucceeded())
let tx = Test.Transaction(
code: "transaction { prepare(acct: &Account) {} execute{} }",
authorizers: [account.address],
signers: [account],
arguments: [],
)
let txResult = Test.executeTransaction(tx)
// If we expect a transaction to fail, we can use Test.beFailed() instead
Test.expect(txResult, Test.beSucceeded())
let err: Test.Error? = txResult.error
if err != nil {
log(err!.message)
}
}Blockchain events
We can also assert that certain events were emitted from the blockchain, up to the latest block.
Suppose we have this contract (Foo.cdc):
access(all)
contract Foo {
access(all)
let msg: String
access(all)
event ContractInitialized(msg: String)
init(_ msg: String) {
self.msg = msg
emit ContractInitialized(msg: self.msg)
}
access(all)
fun sayHello(): String {
return self.msg
}
}import Test
access(all)
fun setup() {
let err = Test.deployContract(
name: "Foo",
path: "./Foo.cdc",
arguments: ["hello from args"],
)
Test.expect(err, Test.beNil())
// As of now, we have to construct the composite type by hand,
// until the testing framework allows developers to import
// contract types, e.g.:
// let typ = Type<FooContract.ContractInitialized>()
let typ = CompositeType("A.01cf0e2f2f715450.Foo.ContractInitialized")!
let events = Test.eventsOfType(typ)
Test.assertEqual(1, events.length)
// We can also fetch all events emitted from the blockchain
log(Test.events())
}Commonly used contracts
The commonly used contracts are already deployed on the blockchain and can be imported without any additional setup.
Suppose this script is saved in get_type_ids.cdc:
import "FungibleToken"
import "FlowToken"
import "NonFungibleToken"
import "MetadataViews"
import "ViewResolver"
import "ExampleNFT"
import "NFTStorefrontV2"
import "NFTStorefront"
access(all)
fun main(): [String] {
return [
Type<FlowToken>().identifier,
Type<NonFungibleToken>().identifier,
Type<MetadataViews>().identifier
]
}import Test
access(all)
fun testExample() {
let script = Test.readFile("get_type_ids.cdc")
let scriptResult = Test.executeScript(script, [])
Test.expect(scriptResult, Test.beSucceeded())
let returnValue = scriptResult.returnValue! as! [String]
let expected = [
"A.0ae53cb6e3f42a79.FlowToken",
"A.f8d6e0586b0a20c7.NonFungibleToken",
"A.f8d6e0586b0a20c7.MetadataViews"
]
Test.assertEqual(expected, returnValue)
}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.
let contractCode = Test.readFile("./sample/contracts/FooContract.cdc")readFile returns the content of the file as a string.
Logging
The log function is available for usage both in test scripts, as well as contracts/scripts/transactions.
The Test.logs() method aggregates all logs from contracts/scripts/transactions.
import Test
access(all)
let account = Test.createAccount()
access(all)
fun testExample() {
let tx = Test.Transaction(
code: "transaction { prepare(acct: &Account) {} execute{ log(\"in a transaction\") } }",
authorizers: [account.address],
signers: [account],
arguments: [],
)
let txResult = Test.executeTransaction(tx)
Test.expect(txResult, Test.beSucceeded())
Test.assertEqual(["in a transaction"], Test.logs())
}Examples
This repository contains some functional examples that demonstrate most of the above features, both for contrived and real-world smart contracts. It also contains a detailed explanation about using code coverage from within the testing framework.