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 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 the above four functions are optional.
_34// A `setup` function that always runs before the rest of the test cases._34// Can be used to initialize things that would be used across the test cases._34// e.g: initialling a blockchain backend, initializing a contract, etc._34access(all) fun setup() {_34}_34_34// The `beforeEach` function runs before each test case. Can be used to perform_34// some state cleanup before each test case, among other things._34access(all) fun beforeEach() {_34}_34_34// The `afterEach` function runs after each test case. Can be used to perform_34// some state cleanup after each test case, among other things._34access(all) fun afterEach() {_34}_34_34// Valid test functions start with the 'test' prefix._34access(all) fun testSomething() {_34}_34_34access(all) fun testAnotherThing() {_34}_34_34access(all) fun testMoreThings() {_34}_34_34// Test functions cannot have any arguments or return values._34access(all) fun testInvalidSignature(message: String): Bool {_34}_34_34// A `tearDown` function that always runs at the end of all test cases._34// e.g: Can be used to stop the blockchain back-end used for tests, etc. or any cleanup._34access(all) fun tearDown() {_34}
Test Standard Library
The testing framework can be used by importing the built-in Test
contract:
_10import Test
Assertions
Test.assert
_10fun assert(_ condition: Bool, message: String)
Fails a test-case if the given condition is false, and reports a message which explains why the condition is false.
The message argument is optional.
_10import Test_10_10access(all) fun testExample() {_10 Test.assert(2 == 2)_10 Test.assert([1, 2, 3].length == 0, message: "Array length is not 0")_10}
Test.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.
_10import Test_10_10access(all) fun testExample() {_10 let array = [1, 2, 3]_10_10 if array.length != 0 {_10 Test.fail(message: "Array length is not 0")_10 }_10}
Test.expect
_10fun 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.
_10import Test_10_10access(all) fun testExample() {_10 let array = [1, 2, 3]_10_10 Test.expect(array.length, Test.equal(3))_10}
Test.assertEqual
_10fun assertEqual(_ expected: AnyStruct, _ actual: AnyStruct)
The assertEqual
function fails the test-case if the given values are not equal, and
reports a message which explains how the two values differ.
_36import Test_36_36access(all) struct Foo {_36 access(all) let answer: Int_36_36 init(answer: Int) {_36 self.answer = answer_36 }_36}_36_36access(all) fun testExample() {_36 Test.assertEqual("this string", "this string")_36 Test.assertEqual(21, 21)_36 Test.assertEqual(true, true)_36 Test.assertEqual([1, 2, 3], [1, 2, 3])_36 Test.assertEqual(_36 {1: true, 2: false, 3: true},_36 {1: true, 2: false, 3: true}_36 )_36_36 let address1 = Address(0xf8d6e0586b0a20c7)_36 let address2 = Address(0xf8d6e0586b0a20c7)_36 Test.assertEqual(address1, address2)_36_36 let foo1 = Foo(answer: 42)_36 let foo2 = Foo(answer: 42)_36_36 Test.assertEqual(foo1, foo2)_36_36 let number1: Int64 = 100_36 let number2: UInt64 = 100_36 // Note that the two values need to have exactly the same type,_36 // and not just value, otherwise the assertion fails:_36 // assertion failed: not equal: expected: 100, actual: 100_36 Test.assertEqual(number1, number2)_36}
Test.expectFailure
_10fun 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.
_24import Test_24_24access(all) struct Foo {_24 access(self) let answer: UInt8_24_24 init(answer: UInt8) {_24 self.answer = answer_24 }_24_24 access(all) fun correctAnswer(_ input: UInt8): Bool {_24 if self.answer != input {_24 panic("wrong answer!")_24 }_24 return true_24 }_24}_24_24access(all) fun testExample() {_24 let foo = Foo(answer: 42)_24_24 Test.expectFailure(fun(): Void {_24 foo.correctAnswer(43)_24 }, errorMessageSubstring: "wrong answer!")_24}
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:
_31import Test_31_31access(all) fun testExample() {_31 let isNegative = Test.newMatcher(fun (_ value: Int): Bool {_31 return value < 0_31 })_31_31 Test.expect(-15, isNegative)_31 // Alternatively, we can use `Test.assert` and the matcher's `test` function._31 Test.assert(isNegative.test(-15), message: "number is not negative")_31}_31_31access(all) fun testCustomMatcherUntyped() {_31 let matcher = Test.newMatcher(fun (_ value: AnyStruct): Bool {_31 if !value.getType().isSubtype(of: Type<Int>()) {_31 return false_31 }_31_31 return (value as! Int) > 5_31 })_31_31 Test.expect(8, matcher)_31}_31_31access(all) fun testCustomMatcherTyped() {_31 let matcher = Test.newMatcher<Int>(fun (_ value: Int): Bool {_31 return value == 7_31 })_31_31 Test.expect(7, matcher)_31}
The Test
contract provides some built-in matcher functions for convenience.
Test.equal
_10fun equal(_ value: AnyStruct): Matcher
The equal
function returns a matcher that succeeds if the tested value is equal to the given value.
Accepts an AnyStruct
value.
_10import Test_10_10access(all) fun testExample() {_10 let array = [1, 2, 3]_10_10 Test.expect([1, 2, 3], Test.equal(array))_10}
Test.beGreaterThan
_10fun beGreaterThan(_ value: Number): Matcher
The beGreaterThan
function returns a matcher that succeeds if the tested value is a number and
greater than the given number.
_10import Test_10_10access(all) fun testExample() {_10 let str = "Hello, there"_10_10 Test.expect(str.length, Test.beGreaterThan(5))_10}
Test.beLessThan
_10fun beLessThan(_ value: Number): Matcher
The beLessThan
function returns a matcher that succeeds if the tested value is a number and
less than the given number.
_10import Test_10_10access(all) fun testExample() {_10 let str = "Hello, there"_10_10 Test.expect(str.length, Test.beLessThan(15))_10}
Test.beNil
_10fun beNil(): Matcher
The beNil
function returns a new matcher that checks if the given test value is nil.
_10import Test_10_10access(all) fun testExample() {_10 let message: String? = nil_10_10 Test.expect(message, Test.beNil())_10}
Test.beEmpty
_10fun beEmpty(): Matcher
The beEmpty
function returns a matcher that succeeds if the tested value is an array or dictionary,
and the tested value contains no elements.
_11import Test_11_11access(all) fun testExample() {_11 let array: [String] = []_11_11 Test.expect(array, Test.beEmpty())_11_11 let dictionary: {String: String} = {}_11_11 Test.expect(dictionary, Test.beEmpty())_11}
Test.haveElementCount
_10fun haveElementCount(_ count: Int): Matcher
The haveElementCount
function returns a matcher that succeeds if the tested value is an array or dictionary,
and has the given number of elements.
_11import Test_11_11access(all) fun testExample() {_11 let array: [String] = ["one", "two", "three"]_11_11 Test.expect(array, Test.haveElementCount(3))_11_11 let dictionary: {String: Int} = {"one": 1, "two": 2, "three": 3}_11_11 Test.expect(dictionary, Test.haveElementCount(3))_11}
Test.contain
_10fun contain(_ element: AnyStruct): Matcher
The 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.
_10access(all) fun testExample() {_10 let array: [String] = ["one", "two", "three"]_10_10 Test.expect(array, Test.contain("one"))_10_10 let dictionary: {String: Int} = {"one": 1, "two": 2, "three": 3}_10_10 Test.expect(dictionary, Test.contain("two"))_10}
Test.beSucceeded
_10fun beSucceeded(): Matcher
The 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.
_12import Test_12_12access(all) fun testExample() {_12 let blockchain = Test.newEmulatorBlockchain()_12 let result = blockchain.executeScript(_12 "access(all) fun main(): Int { return 2 + 3 }",_12 []_12 )_12_12 Test.expect(result, Test.beSucceeded())_12 Test.assertEqual(5, result.returnValue! as! Int)_12}
Test.beFailed
_10fun beFailed(): Matcher
The 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.
_17import Test_17_17access(all) fun testExample() {_17 let blockchain = Test.newEmulatorBlockchain()_17 let account = blockchain.createAccount()_17_17 let tx = Test.Transaction(_17 code: "transaction { execute{ panic(\"some error\") } }",_17 authorizers: [],_17 signers: [account],_17 arguments: [],_17 )_17_17 let result = blockchain.executeTransaction(tx)_17_17 Test.expect(result, Test.beFailed())_17}
Matcher combinators
The built-in matchers, as well as custom matchers, can be combined with the three available combinators:
not
,or
,and
in order to create more elaborate matchers and increase re-usability.
not
_10fun not(_ matcher: Matcher): Matcher
The not
function returns a new matcher that negates the test of the given matcher.
_14import Test_14_14access(all) fun testExample() {_14 let isEven = Test.newMatcher<Int>(fun (_ value: Int): Bool {_14 return value % 2 == 0_14 })_14_14 Test.expect(8, isEven)_14 Test.expect(7, Test.not(isEven))_14_14 let isNotEmpty = Test.not(Test.beEmpty())_14_14 Test.expect([1, 2, 3], isNotEmpty)_14}
or
_10fun or(_ other: Matcher): Matcher
The 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.
_10import Test_10_10access(all) fun testExample() {_10 let one = Test.equal(1)_10 let two = Test.equal(2)_10_10 let oneOrTwo = one.or(two)_10_10 Test.expect(2, oneOrTwo)_10}
and
_10fun and(_ other: Matcher): Matcher
The Matcher.and
function combines this matcher with the given matcher.
Returns a new matcher that succeeds if this and the given matcher succeed.
_14import Test_14_14access(all) fun testExample() {_14 let sevenOrMore = Test.newMatcher<Int>(fun (_ value: Int): Bool {_14 return value >= 7_14 })_14 let lessThanTen = Test.newMatcher<Int>(fun (_ value: Int): Bool {_14 return value <= 10_14 })_14_14 let betweenSevenAndTen = sevenOrMore.and(lessThanTen)_14_14 Test.expect(8, betweenSevenAndTen)_14}
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.
_133/// Blockchain emulates a real network._133///_133access(all) struct Blockchain {_133_133 access(all) let backend: AnyStruct{BlockchainBackend}_133_133 init(backend: AnyStruct{BlockchainBackend}) {_133 self.backend = backend_133 }_133_133 /// Executes a script and returns the script return value and the status._133 /// `returnValue` field of the result will be `nil` if the script failed._133 ///_133 access(all) fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult {_133 return self.backend.executeScript(script, arguments)_133 }_133_133 /// Creates a signer account by submitting an account creation transaction._133 /// The transaction is paid by the service account._133 /// The returned account can be used to sign and authorize transactions._133 ///_133 access(all) fun createAccount(): Account {_133 return self.backend.createAccount()_133 }_133_133 /// Add a transaction to the current block._133 ///_133 access(all) fun addTransaction(_ tx: Transaction) {_133 self.backend.addTransaction(tx)_133 }_133_133 /// Executes the next transaction in the block, if any._133 /// Returns the result of the transaction, or nil if no transaction was scheduled._133 ///_133 access(all) fun executeNextTransaction(): TransactionResult? {_133 return self.backend.executeNextTransaction()_133 }_133_133 /// Commit the current block._133 /// Committing will fail if there are un-executed transactions in the block._133 ///_133 access(all) fun commitBlock() {_133 self.backend.commitBlock()_133 }_133_133 /// Executes a given transaction and commits the current block._133 ///_133 access(all) fun executeTransaction(_ tx: Transaction): TransactionResult {_133 self.addTransaction(tx)_133 let txResult = self.executeNextTransaction()!_133 self.commitBlock()_133 return txResult_133 }_133_133 /// Executes a given set of transactions and commits the current block._133 ///_133 access(all) fun executeTransactions(_ transactions: [Transaction]): [TransactionResult] {_133 for tx in transactions {_133 self.addTransaction(tx)_133 }_133_133 var results: [TransactionResult] = []_133 for tx in transactions {_133 let txResult = self.executeNextTransaction()!_133 results.append(txResult)_133 }_133_133 self.commitBlock()_133 return results_133 }_133_133 /// Deploys a given contract, and initializes it with the arguments._133 ///_133 access(all) fun deployContract(_133 name: String,_133 code: String,_133 account: Account,_133 arguments: [AnyStruct]_133 ): Error? {_133 return self.backend.deployContract(_133 name: name,_133 code: code,_133 account: account,_133 arguments: arguments_133 )_133 }_133_133 /// Set the configuration to be used by the blockchain._133 /// Overrides any existing configuration._133 ///_133 access(all) fun useConfiguration(_ configuration: Configuration) {_133 self.backend.useConfiguration(configuration)_133 }_133_133 /// Returns all the logs from the blockchain, up to the calling point._133 ///_133 access(all) fun logs(): [String] {_133 return self.backend.logs()_133 }_133_133 /// Returns the service account of the blockchain. Can be used to sign_133 /// transactions with this account._133 ///_133 access(all) fun serviceAccount(): Account {_133 return self.backend.serviceAccount()_133 }_133_133 /// Returns all events emitted from the blockchain._133 ///_133 access(all) fun events(): [AnyStruct] {_133 return self.backend.events(nil)_133 }_133_133 /// Returns all events emitted from the blockchain,_133 /// filtered by type._133 ///_133 access(all) fun eventsOfType(_ type: Type): [AnyStruct] {_133 return self.backend.events(type)_133 }_133_133 /// Resets the state of the blockchain to the given height._133 ///_133 access(all) fun reset(to height: UInt64) {_133 self.backend.reset(to: height)_133 }_133_133 /// Moves the time of the blockchain by the given delta,_133 /// which should be passed in the form of seconds._133 ///_133 access(all) fun moveTime(by delta: Fix64) {_133 self.backend.moveTime(by: delta)_133 }_133}
The BlockchainBackend
provides the actual functionality of the blockchain.
_33/// BlockchainBackend is the interface to be implemented by the backend providers._33///_33access(all) struct interface BlockchainBackend {_33_33 access(all) fun executeScript(_ script: String, _ arguments: [AnyStruct]): ScriptResult_33_33 access(all) fun createAccount(): Account_33_33 access(all) fun addTransaction(_ tx: Transaction)_33_33 access(all) fun executeNextTransaction(): TransactionResult?_33_33 access(all) fun commitBlock()_33_33 access(all) fun deployContract(_33 name: String,_33 code: String,_33 account: Account,_33 arguments: [AnyStruct]_33 ): Error?_33_33 access(all) fun useConfiguration(_ configuration: Configuration)_33_33 access(all) fun logs(): [String]_33_33 access(all) fun serviceAccount(): Account_33_33 access(all) fun events(_ type: Type?): [AnyStruct]_33_33 access(all) fun reset(to height: UInt64)_33_33 access(all) fun moveTime(by delta: Fix64)_33}
Creating a blockchain
A new blockchain instance can be created using the Test.newEmulatorBlockchain
method.
It returns a Blockchain
which is backed by a new Flow Emulator instance.
_10import Test_10_10access(all) 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.
_10import Test_10_10access(all) let blockchain = Test.newEmulatorBlockchain()_10access(all) let account = blockchain.createAccount()_10_10access(all) fun testExample() {_10 log(account.address)_10}
Running the above command, from the command-line, we would get:
_10flow test tests/test_sample_usage.cdc_103:31PM DBG LOG: 0x01cf0e2f2f715450_10_10Test results: "tests/test_sample_usage.cdc"_10- PASS: testExample
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.
_19import Test_19_19access(all) let blockchain = Test.newEmulatorBlockchain()_19_19access(all) fun testExample() {_19 let code = "access(all) fun main(name: String): String { return \"Hello, \".concat(name) }"_19 let args = ["Peter"]_19_19 let scriptResult = blockchain.executeScript(code, args)_19_19 // Assert that the script was successfully executed._19 Test.expect(scriptResult, Test.beSucceeded())_19_19 // returnValue has always the type `AnyStruct`,_19 // so we need to type-cast accordingly._19 let returnValue = scriptResult.returnValue! as! String_19_19 Test.assertEqual("Hello, Peter", returnValue)_19}
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 &Account
parameters in the prepare
block of the transaction.
_39import Test_39_39access(all) let blockchain = Test.newEmulatorBlockchain()_39access(all) let account = blockchain.createAccount()_39_39// There are two ways to execute the created transaction._39_39access(all) fun testExample() {_39 let tx = Test.Transaction(_39 code: "transaction { prepare(acct: &Account) {} execute{} }",_39 authorizers: [account.address],_39 signers: [account],_39 arguments: [],_39 )_39_39 // Executing the transaction immediately_39 // This may fail if the current block contains_39 // transactions that have not being executed yet._39 let txResult = blockchain.executeTransaction(tx)_39_39 Test.expect(txResult, Test.beSucceeded())_39}_39_39access(all) fun testExampleTwo() {_39 let tx = Test.Transaction(_39 code: "transaction { prepare(acct: &Account) {} execute{} }",_39 authorizers: [account.address],_39 signers: [account],_39 arguments: [],_39 )_39_39 // Add to the current block_39 blockchain.addTransaction(tx)_39_39 // Execute the next transaction in the block_39 let txResult = blockchain.executeNextTransaction()!_39_39 Test.expect(txResult, Test.beSucceeded())_39}
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.
_20import Test_20_20access(all) let blockchain = Test.newEmulatorBlockchain()_20access(all) let account = blockchain.createAccount()_20_20access(all) fun testExample() {_20 let tx = Test.Transaction(_20 code: "transaction { prepare(acct: &Account) {} execute{} }",_20 authorizers: [account.address],_20 signers: [account],_20 arguments: [],_20 )_20_20 blockchain.commitBlock()_20_20 blockchain.addTransaction(tx)_20_20 // This will fail with `error: internal error: pending block with ID 1f9...c0b7740d2 cannot be committed before execution`_20 blockchain.commitBlock()_20}
Deploying contracts
A contract can be deployed using the deployContract
function of the Blockchain
.
Suppose we have this contract (Foo.cdc
):
_11access(all) contract Foo {_11 access(all) let msg: String_11_11 init(_ msg: String) {_11 self.msg = msg_11 }_11_11 access(all) fun sayHello(): String {_11 return self.msg_11 }_11}
_16import Test_16_16access(all) let blockchain = Test.newEmulatorBlockchain()_16access(all) let account = blockchain.createAccount()_16_16access(all) fun testExample() {_16 let contractCode = Test.readFile("Foo.cdc")_16 let err = blockchain.deployContract(_16 name: "Foo",_16 code: contractCode,_16 account: account,_16 arguments: ["hello from args"],_16 )_16_16 Test.expect(err, Test.beNil())_16}
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 the above Foo.cdc
contract.
The import location for the contract can be specified using the placeholder "Foo"
.
This placeholder can be any unique string.
Suppose this script is saved in say_hello.cdc
.
_10import "Foo"_10_10access(all) fun main(): String {_10 return Foo.sayHello()_10}
Then, before executing the script, the address mapping can be specified as follows:
_31import Test_31_31access(all) let blockchain = Test.newEmulatorBlockchain()_31access(all) let account = blockchain.createAccount()_31_31access(all) fun setup() {_31 blockchain.useConfiguration(Test.Configuration({_31 "Foo": account.address_31 }))_31_31 let contractCode = Test.readFile("Foo.cdc")_31 let err = blockchain.deployContract(_31 name: "Foo",_31 code: contractCode,_31 account: account,_31 arguments: ["hello from args"],_31 )_31_31 Test.expect(err, Test.beNil())_31}_31_31access(all) fun testExample() {_31 let script = Test.readFile("say_hello.cdc")_31 let scriptResult = blockchain.executeScript(script, [])_31_31 Test.expect(scriptResult, Test.beSucceeded())_31_31 let returnValue = scriptResult.returnValue! as! String_31_31 Test.assertEqual("hello from args", returnValue)_31}
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.) has failed.
It 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
can be asserted against its presence or absence.
_29import Test_29_29access(all) let blockchain = Test.newEmulatorBlockchain()_29access(all) let account = blockchain.createAccount()_29_29access(all) fun testExample() {_29 let script = Test.readFile("say_hello.cdc")_29 let scriptResult = blockchain.executeScript(script, [])_29_29 // If we expect a script to fail, we can use Test.beFailed() instead_29 Test.expect(scriptResult, Test.beSucceeded())_29_29 let tx = Test.Transaction(_29 code: "transaction { prepare(acct: &Account) {} execute{} }",_29 authorizers: [account.address],_29 signers: [account],_29 arguments: [],_29 )_29 let txResult = blockchain.executeTransaction(tx)_29_29 // If we expect a transaction to fail, we can use Test.beFailed() instead_29 Test.expect(txResult, Test.beSucceeded())_29_29 let err: Test.Error? = txResult.error_29_29 if err != nil {_29 log(err!.message)_29 }_29}
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
):
_14access(all) contract Foo {_14 access(all) let msg: String_14_14 access(all) event ContractInitialized(msg: String)_14_14 init(_ msg: String) {_14 self.msg = msg_14 emit ContractInitialized(msg: self.msg)_14 }_14_14 access(all) fun sayHello(): String {_14 return self.msg_14 }_14}
_31import Test_31_31access(all) let blockchain = Test.newEmulatorBlockchain()_31access(all) let account = blockchain.createAccount()_31_31access(all) fun setup() {_31 blockchain.useConfiguration(Test.Configuration({_31 "Foo": account.address_31 }))_31_31 let contractCode = Test.readFile("Foo.cdc")_31 let err = blockchain.deployContract(_31 name: "Foo",_31 code: contractCode,_31 account: account,_31 arguments: ["hello from args"],_31 )_31_31 Test.expect(err, Test.beNil())_31_31 // As of now, we have to construct the composite type by hand,_31 // until the testing framework allows developers to import_31 // contract types, e.g.:_31 // let typ = Type<FooContract.ContractInitialized>()_31 let typ = CompositeType("A.01cf0e2f2f715450.Foo.ContractInitialized")!_31 let events = blockchain.eventsOfType(typ)_31 Test.assertEqual(1, events.length)_31_31 // We can also fetch all events emitted from the blockchain_31 log(blockchain.events())_31}
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
.
_16import "FungibleToken"_16import "FlowToken"_16import "NonFungibleToken"_16import "MetadataViews"_16import "ViewResolver"_16import "ExampleNFT"_16import "NFTStorefrontV2"_16import "NFTStorefront"_16_16access(all) fun main(): [String] {_16 return [_16 Type<FlowToken>().identifier,_16 Type<NonFungibleToken>().identifier,_16 Type<MetadataViews>().identifier_16 ]_16}
_20import Test_20_20access(all) let blockchain = Test.newEmulatorBlockchain()_20_20access(all) fun testExample() {_20 let script = Test.readFile("get_type_ids.cdc")_20 let scriptResult = blockchain.executeScript(script, [])_20_20 Test.expect(scriptResult, Test.beSucceeded())_20_20 let returnValue = scriptResult.returnValue! as! [String]_20_20 let expected = [_20 "A.0ae53cb6e3f42a79.FlowToken",_20 "A.f8d6e0586b0a20c7.NonFungibleToken",_20 "A.f8d6e0586b0a20c7.MetadataViews"_20 ]_20_20 Test.assertEqual(expected, returnValue)_20}
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.
Logging
The log
function is available for usage both in test scripts, as well as contracts/scripts/transactions.
The Blockchain.logs()
method aggregates all logs from contracts/scripts/transactions.
_18import Test_18_18access(all) let blockchain = Test.newEmulatorBlockchain()_18access(all) let account = blockchain.createAccount()_18_18access(all) fun testExample() {_18 let tx = Test.Transaction(_18 code: "transaction { prepare(acct: &Account) {} execute{ log(\"in a transaction\") } }",_18 authorizers: [account.address],_18 signers: [account],_18 arguments: [],_18 )_18_18 let txResult = blockchain.executeTransaction(tx)_18_18 Test.expect(txResult, Test.beSucceeded())_18 Test.assertEqual(["in a transaction"], blockchain.logs())_18}
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 on using code coverage from within the testing framework.