⚠️ Required: Your project must follow the required structure and it must be initialized to use the following functions.
Resolves name alias to a Flow address (0x
prefixed) under the following conditions:
- If an account with a specific name has not been previously accessed, the framework will create a new one and then store it under the provided alias.
- Next time when you call this method, it will grab exactly the same account. This allows you to create several accounts up-front and then use them throughout your code, without worrying that accounts match or trying to store and manage specific addresses.
Name | Type | Description |
---|---|---|
alias | string | The alias to reference or create. |
Type | Description |
---|---|
Address | 0x prefixed address of aliased account |
1import {getAccountAddress} from "@onflow/flow-js-testing"23const main = async () => {4const Alice = await getAccountAddress("Alice")5console.log({Alice})6}78main()
In some cases, you may wish to manually create an account with a particular set of private keys
Pass in the following as a single object with the following keys.
Key | Type | Required | Description |
---|---|---|---|
name | string | No | human-readable name to be associated with created account (will be used for address lookup within getAccountAddress) |
keys | [KeyObject or PublicKey] | No | An array of KeyObjects or PublicKeys to be added to the account upon creation (defaults to the universal private key) |
📣 if
name
field not provided, the account address will not be cached and you will be unable to look it up usinggetAccountAddress
.
Type | Description |
---|---|
Address | 0x prefixed address of created account |
Deploys contract code located inside a Cadence file. Returns the transaction result.
Props object accepts the following fields:
Name | Type | Optional | Description |
---|---|---|---|
name | string | name of the file in contracts folder (with .cdc extension) and name of the contract (please note those should be the same) | |
to | Address | ✅ | (optional) account address, where contract will be deployed. If this is not specified, framework will create new account with randomized alias. |
addressMap | AddressMap | ✅ | (optional) object to use for address mapping of existing deployed contracts |
args | [Any] | ✅ | (optional) arguments, which will be passed to contract initializer. (optional) if template does not expect any arguments. |
update | boolean | ✅ | (optional) whether to update deployed contract. Default: false |
transformers | [CadenceTransformer] | ✅ | (optional) an array of operators to modify the code, before submitting it to network |
Type | Description |
---|---|
ResponseObject | Result of the deploying transaction. |
1import path from "path";2import { init, emulator, deployContractByName } from "@onflow/flow-js-testing";34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence");67await init(basePath);8await emulator.start();910// We will deploy our contract to the address that corresponds to "Alice" alias11const to = await getAccountAddress("Alice");1213// We assume there is a file on "../cadence/contracts/Wallet.cdc" path14const name = "Wallet";1516// Arguments will be processed and type matched in the same order as they are specified17// inside of a contract template18const args = [1337, "Hello", { name: "Alice" }];1920const [deploymentResult, err] = await deployContractByName({ to, name });21console.log({ deploymentResult }, { err });22}2324await emulator.stop();25};2627main();
In a bit more rare case you would want to deploy contract code not from existing template file, but rather
from string representation of it. deployContract
method will help you achieve this.
Deploys contract code specified as string. Returns the transaction result.
Props object accepts the following fields:
Name | Type | Optional | Description |
---|---|---|---|
contractCode | string | string representation of contract | |
name | string | name of the contract to be deployed. Should be the same as the name of the contract provided in contractCode | |
to | Address | ✅ | account address, where contract will be deployed. If this is not specified, framework will create new account with randomized alias. |
addressMap | AddressMap | ✅ | object to use for import resolver. Default: {} |
args | [Any] | ✅ | arguments, which will be passed to contract initializer. Default: [] |
update | boolean | ✅ | whether to update deployed contract. Default: false |
transformers | [CadenceTransformer] | ✅ | an array of operators to modify the code, before submitting it to network |
Type | Description |
---|---|
ResponseObject | Result of the deploying transaction. |
1import path from "path"2import {3init,4emulator,5getAccountAddress,6deployContract,7executeScript,8} from "@onflow/flow-js-testing"9;(async () => {10const basePath = path.resolve(__dirname, "../cadence")1112await init(basePath)13await emulator.start()1415// We can specify, which account will hold the contract16const to = await getAccountAddress("Alice")1718const name = "Wallet"19const code = `20pub contract Wallet{21pub let balance: UInt22init(balance: UInt){23self.balance = balance24}25}26`27const args = [1337]2829await deployContract({to, name, code, args})3031const [balance, err] = await executeScript({32code: `33import Wallet from 0x0134pub fun main(): UInt{35return Wallet.balance36}37`,38})39console.log({balance}, {err})4041await emulator.stop()42})()
While framework have automatic import resolver for Contracts you might want to know where it's currently deployed.
We provide a method getContractAddress
for this.
Returns address of the account where the contract is currently deployed.
Name | Type | Description |
---|---|---|
name | string | name of the contract |
Type | Description |
---|---|
Address | 0x prefixed address |
1import path from "path"2import {init, emulator, deployContractByName, getContractAddress} from "../src"3;(async () => {4const basePath = path.resolve(__dirname, "./cadence")56await init(basePath)7await emulator.start()89// if we omit "to" it will be deployed to Service Account10// but let's pretend we don't know where it will be deployed :)11await deployContractByName({name: "Hello"})1213const contractAddress = await getContractAddress("Hello")14console.log({contractAddress})1516await emulator.stop()17})()
📣 Framework does not support contracts with identical names deployed to different accounts. While you can deploy contract to a new address, the internal system, which tracks where contracts are deployed, will only store last address.
The pubFlowKey
method exported by Flow JS Testing Library will generate an RLP-encoded public key given a private key, hashing algorithm, signing algorithm, and key weight.
Name | Type | Optional | Description |
---|---|---|---|
keyObject | KeyObject | ✅ | an object containing a private key & the key's hashing/signing information |
If keyObject
is not provided, Flow JS Testing will default to the universal private key.
Type | Description |
---|---|
Buffer | RLP-encoded public key |
1import {pubFlowKey}23const key = {4privateKey: "a1b2c3" // private key as hex string5hashAlgorithm: HashAlgorithm.SHA3_2566signatureAlgorithm: SignatureAlgorithm.ECDSA_P2567weight: 10008}910const pubKey = await pubFlowKey(key) // public key generated from keyObject provided11const genericPubKey = await pubFlowKey() // public key generated from universal private key/service key
The signUserMessage
method will produce a user signature of some arbitrary data using a particular signer.
Name | Type | Optional | Description |
---|---|---|---|
msgHex | string or Buffer | a hex-encoded string or Buffer which will be used to generate the signature | |
signer | Address or SignerInfo | ✅ | Address or SignerInfo object representing user to generate this signature for (default: universal private key) |
domainTag | string | ✅ | Domain separation tag provided as a utf-8 encoded string (default: no domain separation tag). See more about domain tags here. |
Type | Description |
---|---|
SignatureObject | An object representing the signature for the message & account/keyId which signed for this message |
1import {signUserMessage, getAccountAddress} from "@onflow/flow-js-testing"23const Alice = await getAccountAddress("Alice")4const msgHex = "a1b2c3"56const signature = await generateUserSignature(msgHex, Alice)
Used to verify signatures generated by signUserMessage
. This function takes an array of signatures and verifies that the total key weight sums to >= 1000 and that these signatures are valid.
Name | Type | Optional | Description |
---|---|---|---|
msgHex | string | the message which the provided signatures correspond to provided as a hex-encoded string or Buffer | |
signatures | [SignatureObject] | An array of SignatureObjects which will be verified against this message | |
domainTag | string | ✅ | Domain separation tag provided as a utf-8 encoded string (default: no domain separation tag). See more about domain tags here. |
This method returns an object with the following keys:
Type | Description |
---|---|
boolean | Returns true if signatures are valid and total weight >= 1000 |
1import {2signUserMessage,3verifyUserSignatures,4getAccountAddress,5} from "@onflow/flow-js-testing"67const Alice = await getAccountAddress("Alice")8const msgHex = "a1b2c3"910const signature = await generateUserSignature(msgHex, Alice)1112console.log(await verifyUserSignatures(msgHex, Alice)) // true1314const Bob = await getAccountAddress("Bob")15console.log(await verifyUserSignatures(msgHex, Bob)) // false
Flow Javascript Testing Framework exposes emulator
singleton allowing you to run and stop emulator instance
programmatically. There are two methods available on it.
Starts emulator on a specified port. Returns Promise.
Name | Type | Optional | Description |
---|---|---|---|
options | EmulatorOptions | ✅ | an object containing options for starting the emulator |
Key | Type | Optional | Description |
---|---|---|---|
logging | boolean | ✅ | whether log messages from emulator shall be added to the output (default: false) |
flags | string | ✅ | custom command-line flags to supply to the emulator (default: no flags) |
adminPort | number | ✅ | override the port which the emulator will run the admin server on (default: auto) |
restPort | number | ✅ | override the port which the emulator will run the REST server on (default: auto) |
grpcPort | number | ✅ | override the port which the emulator will run the GRPC server on (default: auto) |
Type | Description |
---|---|
Promise | Promise, which resolves to true if emulator started successfully |
1import path from "path"2import {emulator, init} from "../src"3;(async () => {4const basePath = path.resolve(__dirname, "../cadence")56await init(basePath)78// Start emulator instance on port 80809await emulator.start()10console.log("emulator is working")1112// Stop running emulator13await emulator.stop()14console.log("emulator has been stopped")15})()
Stops emulator instance. Returns Promise.
This method does not expect any arguments.
Type | Description |
---|---|
Promise | Promise, which resolves to true if emulator stopped without issues |
1import {emulator, init} from "@onflow/flow-js-testing"23describe("test setup", () => {4// Instantiate emulator and path to Cadence files5beforeEach(async () => {6const basePath = path.resolve(__dirname, "../cadence")78await init(basePath)9await emulator.start()10})1112// Stop emulator, so it could be restarted13afterEach(async () => {14await emulator.stop()15})16})
Set logging flag on emulator, allowing to temporally enable/disable logging.
Name | Type | Description |
---|---|---|
newState | boolean | Enable/disable logging |
Method does not return anything.
1import path from "path"2import {emulator, init} from "@onflow/flow-js-testing"34describe("test setup", () => {5// Instantiate emulator and path to Cadence files6beforeEach(async () => {7const basePath = path.resolve(__dirname, "../cadence")89await init(basePath)10await emulator.start()11})1213// Stop emulator, so it could be restarted14afterEach(async () => {15await emulator.stop()16})1718test("basic test", async () => {19// Turn on logging from begining20emulator.setLogging(true)21// some asserts and interactions2223// Turn off logging for later calls24emulator.setLogging(false)25// more asserts and interactions here26})27})
Some actions on the network will require account to have certain amount of FLOW token - transaction and storage fees, account creation, etc.
Framework provides a method to query balance with getFlowBalance
and mint new tokens via mintFlow
. You can find
information how to use them below.
Fetch current FlowToken balance of account specified by address
Name | Type | Description |
---|---|---|
address | Address | address of the account to check |
Type | Description |
---|---|
string | UFix64 amount of FLOW tokens stored in account storage represented as string |
1import {2init,3emulator,4getAccountAddress,5getFlowBalance,6} from "@onflow/flow-js-testing"78const main = async () => {9const basePath = path.resolve(__dirname, "../cadence")1011await init(basePath)12await emulator.start()1314const Alice = await getAccountAddress("Alice")1516const [result, error] = await getFlowBalance(Alice)17console.log({result}, {error})1819await emulator.stop()20}2122main()
Sends transaction to mint specified amount of FLOW token and send it to recipient.
⚠️ Required: Framework shall be initialized with
init
method for this method to work.
Name | Type | Description |
---|---|---|
recipient | Address | address of the account to check |
amount | string | UFix64 amount of FLOW tokens to mint and send to recipient |
Type | Description |
---|---|
ResponseObject | Transaction result |
1import path from "path"2import {3init,4emulator,5getAccountAddress,6getFlowBalance,7mintFlow,8} from "../src"9;(async () => {10const basePath = path.resolve(__dirname, "./cadence")1112await init(basePath)13await emulator.start()1415// Get address for account with alias "Alice"16const Alice = await getAccountAddress("Alice")1718// Get initial balance19const [initialBalance] = await getFlowBalance(Alice)20console.log(initialBalance)2122// Add 1.0 FLOW tokens to Alice account23await mintFlow(Alice, "1.0")2425// Check updated balance26const updatedBalance = await getFlowBalance(Alice)27console.log({updatedBalance})2829await emulator.stop()30})()
For Framework to operate properly you need to initialize it first.
You can do it with provided init
method.
Initializes framework variables.
Name | Type | Optional | Description |
---|---|---|---|
bastPath | string | path to the folder holding all Cadence template files | |
options | object | ✅ | options object to use during initialization |
Name | Type | Optional | Description |
---|---|---|---|
pkey | ✅ | private key for service account |
Type | Description |
---|---|
Promise | Promise, which resolves to true if framework was initialized properly |
1import path from "path"2import {init} from "@onflow/flow-js-testing"34describe("test setup", () => {5beforeEach(async () => {6const basePath = path.resolve(__dirname, "../cadence")7await init(basePath)89// alternatively you can pass specific port10// await init(basePath, {port: 8085})11})12})
Returns current block offset - amount of blocks added on top of real current block height.
Type | Description |
---|---|
string | number representing amount of blocks added on top of real current block (encoded as string) |
1import path from "path"2import {init, emulator, getBlockOffset} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67init(basePath)8await emulator.start()910const [blockOffset, err] = await getBlockOffset()11console.log({blockOffset}, {err})1213await emulator.stop()14}1516main()
Returns current block offset - amount of blocks added on top of real current block height.
Name | Type | Description |
---|
Type | Description |
---|---|
number | number representing amount of blocks added on top of real current block height |
1import path from "path"2import {3init,4emulator,5executeScript,6getBlockOffset,7setBlockOffset,8sendTransaction,9} from "@onflow/flow-js-testing"1011const main = async () => {12const basePath = path.resolve(__dirname, "../cadence")1314init(basePath)15await emulator.start()1617// Offset current block height by 4218await setBlockOffset(42)1920const [blockOffset, err] = await getBlockOffset()21console.log({blockOffset}, {err})2223// "getCurrentBlock().height" in your Cadence code will be replaced by Manager to a mocked value24const code = `25pub fun main(): UInt64 {26return getCurrentBlock().height27}28`2930const [result, error] = await executeScript({code})31console.log({result}, {error})3233await emulator.stop()34}3536main()
Returns current timestamp offset - amount of seconds added on top of real current timestamp.
Type | Description |
---|---|
number | number representing amount of seconds added on top of real current timestamp |
1import path from "path"2import {init, emulator, getTimestampOffset} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67init(basePath)8await emulator.start()910const [timestampOffset, err] = await getTimestampOffset()11console.log({timestampOffset}, {err})1213await emulator.stop()14}1516main()
Returns current timestamp offset - amount of seconds added on top of real current timestamp.
Name | Type | Description |
---|
Type | Description |
---|---|
number | number representing amount of seconds added on top of real current timestamp |
1import path from "path"2import {3init,4emulator,5executeScript,6getTimestampOffset,7setTimestampOffset,8sendTransaction,9} from "@onflow/flow-js-testing"1011const main = async () => {12const basePath = path.resolve(__dirname, "../cadence")1314init(basePath)15await emulator.start()1617// Offset current timestamp by 10s18await setTimestampOffset(10)1920const [timestampOffset, err] = await getTimestampOffset()21console.log({timestampOffset}, {err})2223// "getCurrentBlock().timestamp" in your Cadence code will be replaced by Manager to a mocked value24const code = `25pub fun main(): UInt64 {26return getCurrentBlock().timestamp27}28`2930const [result, error] = await executeScript({code})31console.log({result}, {error})3233await emulator.stop()34}3536main()
In order to simplify the process even further we've created several Jest-based methods, which will help you to catch thrown errors and ensure your code works as intended.
Ensure transaction does not throw and sealed.
Name | Type | Description |
---|---|---|
ix | Interaction | interaction, either in form of a Promise or function |
Type | Description |
---|---|
ResponseObject | Transaction result |
1import path from "path"2import {3init,4emulator,5shallPass,6sendTransaction,7getAccountAddress,8} from "js-testing-framework"910// We need to set timeout for a higher number, cause some interactions might need more time11jest.setTimeout(10000)1213describe("interactions - sendTransaction", () => {14// Instantiate emulator and path to Cadence files15beforeEach(async () => {16const basePath = path.resolve(__dirname, "./cadence")17await init(basePath)18return emulator.start()19})2021// Stop emulator, so it could be restarted22afterEach(async () => {23return emulator.stop()24})2526test("basic transaction", async () => {27const code = `28transaction(message: String){29prepare(singer: AuthAccount){30log(message)31}32}33`34const Alice = await getAccountAddress("Alice")35const signers = [Alice]36const args = ["Hello, Cadence"]3738const [txResult, error] = await shallPass(39sendTransaction({40code,41signers,42args,43})44)4546// Transaction result will hold status, events and error message47console.log({txResult}, {error})48})49})
Ensure interaction throws an error. Can test for specific error messages or catch any error message if message
is not provided.
Returns Promise, which contains result, when resolved.
Name | Type | Description |
---|---|---|
ix | Interaction | transaction, either in form of a Promise or function |
message (optional) | string or RegExp | expected error message provided as either a string equality or regular expression to match, matches any error by default |
Type | Description |
---|---|
ResponseObject | Transaction result |
1import path from "path"2import {3init,4emulator,5shallPass,6sendTransaction,7getAccountAddress,8} from "js-testing-framework"910// We need to set timeout for a higher number, cause some interactions might need more time11jest.setTimeout(10000)1213describe("interactions - sendTransaction", () => {14// Instantiate emulator and path to Cadence files15beforeEach(async () => {16const basePath = path.resolve(__dirname, "./cadence")17await init(basePath)18return emulator.start()19})2021// Stop emulator, so it could be restarted22afterEach(async () => {23return emulator.stop()24})2526test("basic transaction", async () => {27const code = `28transaction(message: String){29prepare(singer: AuthAccount){30panic("You shall not pass!")31}32}33`34const Alice = await getAccountAddress("Alice")35const signers = [Alice]36const args = ["Hello, Cadence"]3738// Catch any cadence error39let [txResult, error] = await shallRevert(40sendTransaction({41code,42signers,43args,44})45)4647// Catch only specific panic message48let [txResult, error] = await shallRevert(49sendTransaction({50code,51signers,52args,53}),54"You shall not pass!"55)5657// Transaction result will hold status, events and error message58console.log({txResult}, {error})59})60})
Ensure interaction resolves without throwing errors.
Name | Type | Description |
---|---|---|
ix | Interaction | interaction, either in form of a Promise or function |
Type | Description |
---|---|
InteractionResult | Interaction result |
1import path from "path"2import {init, emulator, shallPass, executeScript} from "js-testing-framework"34// We need to set timeout for a higher number, cause some interactions might need more time5jest.setTimeout(10000)67describe("interactions - sendTransaction", () => {8// Instantiate emulator and path to Cadence files9beforeEach(async () => {10const basePath = path.resolve(__dirname, "./cadence")11await init(basePath)12return emulator.start()13})1415// Stop emulator, so it could be restarted16afterEach(async () => {17return emulator.stop()18})1920test("basic script", async () => {21const code = `22pub fun main():Int{23return 4224}25`2627const [result, error] = await shallResolve(28executeScript({29code,30})31)3233expect(result).toBe(42)34expect(error).toBe(null)35})36})
It is often the case that you need to query current state of the network. For example, to check balance of the account, read public value of the contract or ensure that user has specific resource in their storage.
We abstract this interaction into single method called executeScript
. Method have 2 different signatures.
⚠️ Required: Your project must follow the required structure it must be initialized to use the following functions.
Provides explicit control over how you pass values.
props
object accepts following fields:
Name | Type | Optional | Description |
---|---|---|---|
code | string | ✅ | string representation of Cadence script |
name | string | ✅ | name of the file in scripts folder to use (sans .cdc extension) |
args | [any] | ✅ | an array of arguments to pass to script. Optional if script does not expect any arguments. |
transformers | [CadenceTransformer] | ✅ | an array of operators to modify the code, before submitting it to network |
⚠️ Required: Either
code
orname
field shall be specified. Method will throw an error if both of them are empty. Ifname
field provided, framework will source code from file and override value passed viacode
field.
Type | Description |
---|---|
ResponseObject | Script result |
1import path from "path"2import {init, emulator, executeScript} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67// Init framework8init(basePath)9// Start emulator10await emulator.start()1112// Define code and arguments we want to pass13const code = `14pub fun main(message: String): Int{15log(message)1617return 4218}19`20const args = ["Hello, from Cadence"]2122const [result, error, logs] = await executeScript({code, args})23console.log({result}, {error}, {logs})2425// Stop emulator instance26await emulator.stop()27}2829main()
This signature provides simplified way of executing a script, since most of the time you will utilize existing Cadence files.
Name | Type | Optional | Description |
---|---|---|---|
name | string | name of the file in scripts folder to use (sans .cdc extension) | |
args | [any] | ✅ | an array of arguments to pass to script. Optional if scripts don't expect any arguments. Default: [] |
Type | Description |
---|---|
ResponseObject | Script result |
1import path from "path"2import {init, emulator, executeScript} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67// Init framework8init(basePath)9// Start emulator10await emulator.start()1112// Define arguments we want to pass13const args = ["Hello, from Cadence"]1415// We assume there is a file `scripts/log-message.cdc` under base path16const [result, error, logs] = await executeScript("log-message", args)17console.log({result}, {error}, {logs})1819await emulator.stop()20}2122main()
Another common case is interactions that mutate network state - sending tokens from one account to another, minting new NFT, etc. Framework provides sendTransaction
method to achieve this. This method have 2 different signatures.
⚠️ Required: Your project must follow the required structure it must be initialized to use the following functions.
Send transaction to network. Provides explicit control over how you pass values.
props
object accepts following fields:
Name | Type | Optional | Description |
---|---|---|---|
code | string | ✅ | string representation of Cadence transaction |
name | string | ✅ | name of the file in transaction folder to use (sans .cdc extension) |
args | [any] | ✅ | an array of arguments to pass to transaction. Optional if transaction does not expect any arguments. |
signers | [Address or SignerInfo] | ✅ | an array of Address or SignerInfo objects representing transaction autorizers |
addressMap | AddressMap | ✅ | name/address map to use as lookup table for addresses in import statements |
transformers | [CadenceTransformer] | ✅ | an array of operators to modify the code, before submitting it to network |
⚠️ Required: Either
code
orname
field shall be specified. Method will throw an error if both of them are empty. Ifname
field provided, framework will source code from file and override value passed viacode
field.
📣 if
signers
field not provided, service account will be used to authorize the transaction.
📣 Pass
addressMap
only in cases, when you would want to override deployed contract. Otherwide imports can be resolved automatically without explicitly passing them viaaddressMap
field
1import path from "path"2import {3init,4emulator,5sendTransaction,6getAccountAddress,7} from "@onflow/flow-js-testing"89const main = async () => {10const basePath = path.resolve(__dirname, "../cadence")1112// Init framework13await init(basePath)14// Start emulator15await emulator.start()1617// Define code and arguments we want to pass18const code = `19transaction(message: String){20prepare(signer: AuthAccount){21log(message)22}23}24`25const args = ["Hello, from Cadence"]26const Alice = await getAccountAddress("Alice")27const signers = [Alice]2829const [result, error, logs] = await sendTransaction({code, args, signers})30console.log({result}, {error}, {logs})3132// Stop emulator instance33await emulator.stop()34}3536main()
This signature provides simplified way to send a transaction, since most of the time you will utilize existing Cadence files.
Name | Type | Optional | Description |
---|---|---|---|
name | string | ✅ | name of the file in transaction folder to use (sans .cdc extension) |
args | [any] | ✅ | an array of arguments to pass to transaction. Optional if transaction does not expect any arguments. |
signers | [Address or SignerInfoObject] | ✅ | an array of Address or array of SignerInfoObject representing transaction autorizers |
1import path from "path"2import {3init,4emulator,5sendTransaction,6shallPass,7} from "@onflow/flow-js-testing"89const main = async () => {10const basePath = path.resolve(__dirname, "../cadence")1112// Init framework13await init(basePath)14// Start emulator15await emulator.start()1617// Define arguments we want to pass18const args = ["Hello, Cadence"]1920const [result, error, logs] = await shallPass(21sendTransaction("log-message", [], args)22)23console.log({result}, {error}, {logs})2425// Stop the emulator instance26await emulator.stop()27}2829main()
The philosophy behind Flow JS Testing Framework is to be a set of helper methods. They can be used in opinionated way, envisioned by Flow Team. Or they can work as building blocks, allowing developers to build their own testing solution as they see fit.
Following methods used inside other framework methods, but we feel encouraged to list them here as well.
Returns Cadence template as string with addresses replaced using addressMap
Name | Type | Optional | Description |
---|---|---|---|
file | string | relative (to the place from where the script was called) or absolute path to the file containing the code | |
addressMap | AddressMap | ✅ | object to use for address mapping of existing deployed contracts. Default: {} |
byAddress | boolean | ✅ | whether addressMap is {name:address} or {address:address} type. Default: false |
Type | Description |
---|---|
string | content of a specified file |
1import path from "path"2import {init, getTemplate} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")6await init(basePath)78const template = await getTemplate("../cadence/scripts/get-name.cdc")9console.log({template})10}1112main()
Returns Cadence template from file with name
in _basepath_/contracts
folder
Name | Type | Optional | Description |
---|---|---|---|
name | string | name of the contract template | |
addressMap | AddressMap | ✅ | object to use for address mapping of existing deployed contracts |
Type | Description |
---|---|
string | Cadence template code for specified contract |
1import path from "path"2import {init, emulator, getContractCode} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67await init(basePath)8await emulator.start()910// Let's assume we need to import MessageContract11await deployContractByName({name: "MessageContract"})12const [MessageContract] = await getContractAddress("MessageContract")13const addressMap = {MessageContract}1415const contractTemplate = await getContractCode("HelloWorld", {16MessageContract,17})18console.log({contractTemplate})1920await emulator.stop()21}2223main()
Returns Cadence template from file with name
in _basepath_/transactions
folder
Name | Type | Optional | Description |
---|---|---|---|
name | string | name of the transaction template | |
addressMap | AddressMap | ✅ | object to use for address mapping of existing deployed contracts |
Type | Description |
---|---|
string | Cadence template code for specified transaction |
1import path from "path"2import {init, emulator, getTransactionCode} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67await init(basePath)8await emulator.start()910// Let's assume we need to import MessageContract11await deployContractByName({name: "MessageContract"})12const [MessageContract] = await getContractAddress("MessageContract")13const addressMap = {MessageContract}1415const txTemplate = await getTransactionCode({16name: "set-message",17addressMap,18})19console.log({txTemplate})2021await emulator.stop()22}2324main()
Returns Cadence template from file with name
in _basepath_/scripts
folder
Name | Type | Optional | Description |
---|---|---|---|
name | string | name of the script template | |
addressMap | AddressMap | ✅ | object to use for address mapping of existing deployed contracts |
Type | Description |
---|---|
string | Cadence template code for specified script |
1import path from "path"2import {init, emulator, getScriptCode} from "@onflow/flow-js-testing"34const main = async () => {5const basePath = path.resolve(__dirname, "../cadence")67await init(basePath)8await emulator.start()910// Let's assume we need to import MessageContract11await deployContractByName({name: "MessageContract"})12const [MessageContract] = await getContractAddress("MessageContract")13const addressMap = {MessageContract}1415const scriptTemplate = await getScriptCode({16name: "get-message",17addressMap,18})1920console.log({scriptTemplate})21await emulator.stop()22}2324main()
Object to use for address mapping of existing deployed contracts. Key shall be string
and value shall be Address
1const addressMap = {2Messanger: "0x01cf0e2f2f715450",3Logger: "0x179b6b1cb6755e31",4}
Interaction is a Promise or function returning a promise.
1const ix = async () => {2return new Promise((resolve, reject) => {3setTimeout(() => {4resolve(1337)5})6}, 500)7}
Function, which will get valid Cadence code, modify it and return valid Cadence code
This transformer will look for occupancies of specific import statement and replace it with proper address, where it's deployed on Emulator
1const replaceAddress = async code => {2const modified = code.replace(3/import\s+FungibleToken\s+from\s+0xFUNGIBLETOKEN/,4"import FungibleToken from 0xee82856bf20e2aa6"5)67return modified8}
Key objects are used to specify signer keys when creating accounts.
Key | Required | Value Type | Description |
---|---|---|---|
hashAlgorithm | No | HashAlgorithm | Hashing algorithm to use for generating signatures to be signed by this key (default: HashAlgorithm.SHA3_256 ) |
privateKey | Yes | string | Private key to use to generate the signature |
signatureAlgorithm | No | SignatureAlgorithm | Signing algorithm used to sign transactions with this key (default: SignatureAlgorithm.ECDSA_P256 ) |
weight | No | number | Weight of the key - see Flow Core Concepts for more information |
Public keys are stored as Buffer
objects which have been RLP encoded according to the Flow spec.
In order to generate this object using the Flow JS Testing library, use the pubFlowKey
method exported by the library.
1import {pubFlowKey} from "@onflow/flow-js-testing"23const pubKey = await pubFlowKey({4privateKey: ...,5hashAlgorithm: ...,6signatureAlgorithm: ...,7weight: ...8})
Signature objects are used to represent a signature for a particular message as well as the account and keyId which signed for this message.
Key | Value Type | Description |
---|---|---|
addr | Address | the address of the account which this signature has been generated for |
keyId | number | Address or SignerInfo object representing user to generate this signature for |
signature | string | a hexidecimal-encoded string representation of the generated signature |
Signer Info objects are used to specify information about which signer and which key from this signer shall be used to sign a transaction.
Key | Required | Value Type | Description |
---|---|---|---|
addr | Yes | Address | The address of the signer's account |
hashAlgorithm | No | HashAlgorithm | Hashing algorithm to use for generating the signature (default: HashAlgorithm.SHA3_256 ) |
keyId | No | number | The index of the desired key to use from the signer's account (default: 0 ) |
privateKey | No | string | Private key to use to generate the signature (default: service account private key - this is the default PK for all accounts generated by Flow JS Testing Library, see: accounts) |
signatureAlgorithm | No | SignatureAlgorithm | Signing algorithm used to generate the signature (default: SignatureAlgorithm.ECDSA_P256 ) |
Identifier | Value |
---|---|
SHA2_256 | 1 |
SHA3_256 | 3 |
Hash algorithms may be provided as either an enum (accessible via the HashAlgorithm
object exported by Flow JS Testing, i.e. HashAlgorithm.SHA3_256
) or as a string representation of their enum identifier (i.e. "SHA3_256"
)
Identifier | Value |
---|---|
ECDSA_P256 | 2 |
ECDSA_secp256k1 | 3 |
Signing algorithms may be provided as either an enum (accessible via the SignatureAlgorithm
object exported by Flow JS Testing, i.e. SignatureAlgorithm.ECDSA_P256
) or as a string representation of their enum identifier (i.e. "ECDSA_P256"
)
Returns true if the given string is a validly formatted account address (both "0x" prefixed and non-prefixed are valid)
Name | Type | Optional | Description |
---|---|---|---|
address | string | string to test against the regex |
Type | Description |
---|---|
boolean | Returns true if given string is a validly formatted account address. |
1import {isAddress} from "@onflow/flow-js-testing"23const badAddr = "0xqrtyff"4console.log(isAddress(badAddr)) // false56const goodAddrWithPrefix = "0xf8d6e0586b0a20c1"7console.log(isAddress(goodAddrWithPrefix)) // true89const goodAddrSansPrefix = "f8d6e0586b0a20c1"10console.log(isAddress(goodAddrSansPrefix)) // true