MAINNET:
Loading...
TESTNET:
Loading...
/
onflow.org
Flow Playground

JS Testing API Reference

⚠️ Required: Your project must follow the required structure and it must be initialized to use the following functions.

Accounts

getAccountAddress

Resolves name alias to a Flow address (0x prefixed) under the following conditions:

  • If account with specific name has not been previously accessed framework will first create a new one and then store it under provided alias.
  • Next time when you call this method, it will grab exactly the same account. This allows you to create several accounts first and then use them throughout your code, without worrying that accounts match or trying to store/handle specific addresses.

Arguments

NameTypeDescription
aliasstringThe alias to reference or create.

Returns

TypeDescription
Address0x prefixed address of aliased account

Usage

import { getAccountAddress } from "flow-js-testing";

const main = async () => {
  const Alice = await getAccountAddress("Alice");
  console.log({ Alice });
};

main();

Contracts

deployContractByName(props)

Deploys contract code located inside a Cadence file. Returns the transaction result.\

Arguments

Props object accepts following fields:

NameTypeOptionalDescription
namestringname of the file in contracts folder (with .cdc extension) and name of the contract (please note those should be the same)
toAddress(optional) account address, where contract will be deployed. If this is not specified, framework will create new account with randomized alias.
addressMapAddressMap(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.
updateboolean(optional) whether to update deployed contract. Default: false

Returns

TypeDescription
ResponseObjectResult of the deploying transaction.

Usage:

import path from "path";
import { init, emulator, deployContractByName } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  init(basePath, port);
  await emulator.start(port);

  // We will deploy our contract to the address that corresponds to "Alice" alias
  const to = await getAccountAddress("Alice");

  // We assume there is a file on "../cadence/contracts/Wallet.cdc" path
  const name = "Wallet";

  // Arguments will be processed and type matched in the same order as they are specified
  // inside of a contract template
  const args = [1337, "Hello", { name: "Alice" }];

  try {
    const deploymentResult = await deployContractByName({ to, name });
    console.log({ deploymentResult });
  } catch (e) {
    // If we encounter any errors during teployment, we can catch and process them here
    console.log(e);
  }

  await emulator.stop();
};

main();

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.

deployContract(props)

Deploys contract code specified as string. Returns the transaction result.

Arguments

Props object accepts the following fields:

NameTypeOptionalDescription
contractCodestringstring representation of contract
namestringname of the contract to be deployed. Should be the same as the name of the contract provided in contractCode
toAddressaccount address, where contract will be deployed. If this is not specified, framework will create new account with randomized alias.
addressMapAddressMapobject to use for import resolver. Default: {}
args[Any]arguments, which will be passed to contract initializer. Default: []
updatebooleanwhether to update deployed contract. Default: false

Returns

TypeDescription
ResponseObjectResult of the deploying transaction.

Usage

import path from "path";
import { init, emulator, deployContract } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port, false);

  const to = await getAccountAddress("Alice");
  const name = "Wallet";
  const contractCode = `
        pub contract Wallet{
            init(amount: Int){
                log(amount)
                log("Thank you for the food!")
            }
        }
    `;
  const args = [1337];

  try {
    const deploymentResult = await deployContractByName({
      to,
      name,
      contractCode,
      args,
    });

    console.log({ deploymentResult });
  } catch (e) {
    console.log(e);
  }

  await emulator.stop();
};

main();

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.

getContractAddress(name)

Returns address of the account where the contract is currently deployed.

Arguments

NameTypeDescription
namestringname of the contract

Returns

TypeDescription
Address0x prefixed address

Usage

import { getContractAddress } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port, false);

  // if we ommit "to" it will be deployed to a newly generated address with "unknown" alias
  await deployContractByName({ name: "HelloWorld" });

  const contract = await getContractAddress("HelloWorld");
  console.log({ contract });
};

main();

📣 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.

Emulator

Flow Javascript Testing Framework exposes emulator singleton allowing you to run and stop emulator instance programmatically. There are two methods available on it.

emulator.start(port, logging)

Starts emulator on a specified port. Returns Promise.

Arguments

NameTypeOptionalDescription
portnumbernumber representing a port to use for access API. Default: 8080
loggingbooleanwhether log messages from emulator shall be added to the output

Returns

TypeDescription
PromisePromise, which resolves to true if emulator started successfully

Usage

import { emulator, init } from "flow-js-testing";

describe("test setup", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "../cadence");
    const port = 8080;

    await init(basePath, { port });

    // Start emulator instance on port 8080
    await emulator.start(port);
  });
});

emulator.stop()

Stops emulator instance. Returns Promise.

Arguments

This method does not expect any arguments.

Returns

TypeDescription
PromisePromise, which resolves to true if emulator stopped without issues

Usage

import { emulator, init } from "flow-js-testing";

describe("test setup", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "../cadence");
    const port = 8080;

    await init(basePath, { port });
    await emulator.start(port);
  });

  // Stop emulator, so it could be restarted
  afterEach(async () => {
    await emulator.stop();
  });
});

emulator.setLogging(newState)

Set logging flag on emulator, allowing to temporally enable/disable logging.

Arguments

NameTypeDescription
newStatebooleanEnable/disable logging

Returns

Method does not return anything.

Usage

import { emulator, init } from "flow-js-testing";

describe("test setup", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "../cadence");
    const port = 8080;

    await init(basePath, { port });
    await emulator.start(port);
  });

  // Stop emulator, so it could be restarted
  afterEach(async () => {
    await emulator.stop();
  });

  test("basic test", async () => {
    // Turn on logging from begining
    emulator.setLogging(true);
    // some asserts and interactions

    // Turn off logging for later calls
    emulator.setLogging(false);
    // more asserts and interactions here
  });
});

FLOW Management

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.

getFlowBalance(address)

Fetch current FlowToken balance of account specified by address

Arguments

NameTypeDescription
addressAddressaddress of the account to check

Returns

TypeDescription
stringUFix64 amount of FLOW tokens stored in account storage represented as string

Usage

import { init, emulator, getFlowBalance } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port);

  const Alice = await getAccountAddress("Alice");

  try {
    const result = await getFlowBalance(Alice);
    console.log({ result });
  } catch (e) {
    console.log(e);
  }

  await emulator.stop();
};

main();

mintFlow(recipient, amount)

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.

Arguments

NameTypeDescription
recipientAddressaddress of the account to check
amountstringUFix64 amount of FLOW tokens to mint and send to recipient

Returns

TypeDescription
ResponseObjectTransaction result

Usage

import { init, emulator, mintFlow } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port);

  const Alice = await getAccountAddress("Alice");
  const amount = "42.0";
  try {
    const mintResult = await mintFlow(Alice);
    console.log({ mintResult });
  } catch (e) {
    console.log(e);
  }

  await emulator.stop();
};

main();

Init

For Framework to operate properly you need to initialize it first. You can do it with provided init method.

init( basePath, options)

Initializes framework variables and specifies port to use for HTTP and grpc access. port is set to 8080 by default. grpc port is calculated to 3569 + (port - 8080) to allow multiple instances of emulator to be run in parallel.

Arguments

NameTypeOptionalDescription
bastPathstringpath to the folder holding all Cadence template files
optionsobjectoptions object to use during initialization

Options

NameTypeOptionalDescription
porthttp port for access node
pkeyprivate key for service account

Returns

TypeDescription
PromisePromise, which resolves to true if framework was initialized properly

Usage

import path from "path";
import { init } from "flow-js-testing";

describe("test setup", () => {
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "../cadence");
    await init(basePath);

    // alternatively you can pass specific port
    // await init(basePath, {port: 8085})
  });
});

Jest Helpers

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.

shallPass(ix)

Ensure transaction does not throw and sealed.

Arguments

NameTypeDescription
ixInteractioninteraction, either in form of a Promise or function

Returns

TypeDescription
ResponseObjectTransaction result

Usage

import path from "path";
import {
  init,
  emulator,
  shallPass,
  sendTransaction,
  getAccountAddress,
} from "js-testing-framework";

// We need to set timeout for a higher number, cause some interactions might need more time
jest.setTimeout(10000);

describe("interactions - sendTransaction", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "./cadence");
    const port = 8080;
    await init(basePath, { port });
    return emulator.start(port);
  });

  // Stop emulator, so it could be restarted
  afterEach(async () => {
    return emulator.stop();
  });

  test("basic transaction", async () => {
    const code = `
      transaction(message: String){
        prepare(singer: AuthAccount){
          log(message)
        }
      }
    `;
    const Alice = await getAccountAddress("Alice");
    const signers = [Alice];
    const args = ["Hello, Cadence"];

    const txResult = await shallPass(
      sendTransaction({
        code,
        signers,
        args,
      }),
    );

    // Transaction result will hold status, events and error message
    console.log(txResult);
  });
});

shallRevert(ix)

Ensure interaction throws an error. You might want to use this to test incorrect inputs.

Arguments

NameTypeDescription
ixInteractiontransaction, either in form of a Promise or function

Returns

TypeDescription
ResponseObjectTransaction result

Usage

import path from "path";
import {
  init,
  emulator,
  shallPass,
  sendTransaction,
  getAccountAddress,
} from "js-testing-framework";

// We need to set timeout for a higher number, cause some interactions might need more time
jest.setTimeout(10000);

describe("interactions - sendTransaction", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "./cadence");
    const port = 8080;
    await init(basePath, { port });
    return emulator.start(port);
  });

  // Stop emulator, so it could be restarted
  afterEach(async () => {
    return emulator.stop();
  });

  test("basic transaction", async () => {
    const code = `
      transaction(message: String){
        prepare(singer: AuthAccount){
          panic("You shall not pass!")
        }
      }
    `;
    const Alice = await getAccountAddress("Alice");
    const signers = [Alice];
    const args = ["Hello, Cadence"];

    const txResult = await shallRevert(
      sendTransaction({
        code,
        signers,
        args,
      }),
    );

    // Transaction result will hold status, events and error message
    console.log(txResult);
  });
});

shallResolve(ix)

Ensure interaction resolves without throwing errors.

Arguments

NameTypeDescription
ixInteractioninteraction, either in form of a Promise or function

Returns

TypeDescription
InteractionResultInteraction result

Usage

import path from "path";
import { init, emulator, shallPass, executeScript } from "js-testing-framework";

// We need to set timeout for a higher number, cause some interactions might need more time
jest.setTimeout(10000);

describe("interactions - sendTransaction", () => {
  // Instantiate emulator and path to Cadence files
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "./cadence");
    const port = 8080;
    await init(basePath, { port });
    return emulator.start(port);
  });

  // Stop emulator, so it could be restarted
  afterEach(async () => {
    return emulator.stop();
  });

  test("basic script", async () => {
    const code = `
      pub fun main():Int{
        return 42
      }
    `;

    const result = await shallResolve(
      executeScript({
        code,
      }),
    );

    expect(result).toBe(42);
  });
});

Scripts

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.

executeScript(props)

Provides explicit control over how you pass values.

Arguments

props object accepts following fields:

NameTypeOptionalDescription
codestringstring representation of Cadence script
namestringname of the file in scripts folder to use (sans .cdc extension)
argsarrayan array of arguments to pass to script. Optional if script does not expect any arguments.

⚠️ Required: Either code or name field shall be specified. Method will throw an error if both of them are empty. If name field provided, framework will source code from file and override value passed via code field.

Returns

TypeDescription
ResponseObjectScript result

Usage

import path from "path";
import { init, emulator, executeScript } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  // Init framework
  init(basePath, { port });
  // Start emulator
  await emulator.start(port);

  // Define code and arguments we want to pass
  const code = `
    pub fun main(message: String): Int{
      log(message)

      return 42
    }
  `;
  const args = ["Hello, from Cadence"];

  // If something wrong with script execution method will throw an error,
  // so we need to catch it and process
  try {
    const result = await executeScript({ code, args });
    console.log({ result });
  } catch (e) {
    console.error(e);
  }

  // Stop emulator instance
  await emulator.stop();
};

main();

executeScript(name: string, args: [any])

This signature provides simplified way of executing a script, since most of the time you will utilize existing Cadence files.

Arguments

NameTypeOptionalDescription
namestringname of the file in scripts folder to use (sans .cdc extension)
argsarrayan array of arguments to pass to script. Optional if scripts don't expect any arguments. Default: []

Returns

TypeDescription
ResponseObjectScript result

Usage

import path from "path";
import { init, emulator, executeScript } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  // Init framework
  init(basePath, port);
  // Start emulator
  await emulator.start(port, false);

  // Define arguments we want to pass
  const args = ["Hello, from Cadence"];

  // If something wrong with script execution method will throw an error,
  // so we need to catch it and process
  try {
    // We assume there is a file `scripts/log-message.cdc` under base path
    const result = await executeScript("log-message", args);
    console.log({ result });
  } catch (e) {
    console.error(e);
  }

  await emulator.stop();
};

main();

Transactions

Another common case is necessity to mutate network state - sending tokens from one account to another, minting new NFT, etc. Framework provides sendTransaction method to achieve this. This method has 2 different signatures.

⚠️ Required: Your project must follow the required structure it must be initialized to use the following functions.

sendTransaction(props)

Send transaction to network. Provides explicit control over how you pass values.

Arguments

props object accepts following fields:

NameTypeOptionalDescription
codestringstring representation of Cadence transaction
namestringname 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]an array of Address representing transaction autorizers
addressMapAddressMapname/address map to use as lookup table for addresses in import statements

⚠️ Required: Either code or name field shall be specified. Method will throw an error if both of them are empty. If name field provided, framework will source code from file and override value passed via code 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 via addressMap field

Returns

TypeDescription
ResponseObjectInteraction result

Usage

import path from "path";
import { init, emulator, sendTransaction, getAccountAddress } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  // Init framework
  await init(basePath, { port });
  // Start emulator
  await emulator.start(port);

  // Define code and arguments we want to pass
  const code = `
    transaction(message: String){
      prepare(signer: AuthAccount){
        log(message)
      }
    }
  `;
  const args = ["Hello, from Cadence"];
  const Alice = await getAccountAddress("Alice");
  const signers = [Alice];

  // If something wrong with transaction execution method will throw an error,
  // so we need to catch it and process
  try {
    const tx = await sendTransaction({ code, args, signers });
    console.log({ tx });
  } catch (e) {
    console.error(e);
  }

  // Stop emulator instance
  await emulator.stop();
};

main();

sendTransaction(name, signers, args)

This signature provides simplified way to send a transaction, since most of the time you will utilize existing Cadence files.

NameTypeOptionalDescription
namestringname of the file in transaction folder to use (sans .cdc extension)
signersarrayan array of Address representing transaction autorizers
args[Any]an array of arguments to pass to transaction. Optional if transaction does not expect any arguments.

Returns

TypeDescription
ResponseObjectInteraction result

Usage

import path from "path";
import { init, emulator, sendTransaction } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  // Init framework
  await init(basePath, { port });
  // Start emulator
  await emulator.start(port);

  // Define arguments we want to pass
  const args = ["Hello, Cadence"];

  try {
    const tx = await sendTransaction("log-message", args);
    console.log({ tx });
  } catch (e) {
    console.error(e);
  }
};

main();

Templates

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.

getTemplate(file, addressMap, byAddress)

Returns Cadence template as string with addresses replaced using addressMap

NameTypeOptionalDescription
filestringrelative (to the place from where the script was called) or absolute path to the file containing the code
addressMapAddressMapobject to use for address mapping of existing deployed contracts. Default: {}
byAddressbooleanwhether addressMap is {name:address} or {address:address} type. Default: false

Returns

TypeDescription
stringcontent of a specified file

Usage

import path from "path";
import { init, getTemplate } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  init(basePath);

  const template = await getTemplate("../cadence/scripts/get-name.cdc");
  console.log({ template });
};

main();

getContractCode(name, addressMap)

Returns Cadence template from file with name in _basepath_/contracts folder

Arguments

NameTypeOptionalDescription
namestringname of the contract template
addressMapAddressMapobject to use for address mapping of existing deployed contracts

Returns

TypeDescription
stringCadence template code for specified contract

Usage

import path from "path";
import { init, emulator, getContractCode } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port);

  // Let's assume we need to import MessageContract
  await deployContractByName({ name: "MessageContract" });
  const MessageContract = await getContractAddress("MessageContract");
  const addressMap = { MessageContract };

  const contractTemplate = await getContractCode("HelloWorld", {
    MessageContract,
  });
  console.log({ contractTemplate });

  await emulator.stop();
};

main();

getTransactionCode(name, addressMap)

Returns Cadence template from file with name in _basepath_/transactions folder

Arguments

NameTypeOptionalDescription
namestringname of the transaction template
addressMapAddressMapobject to use for address mapping of existing deployed contracts

Returns

TypeDescription
stringCadence template code for specified transaction

Usage

import path from "path";
import { init, emulator, getTransactionCode } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port);

  // Let's assume we need to import MessageContract
  await deployContractByName({ name: "MessageContract" });
  const MessageContract = await getContractAddress("MessageContract");
  const addressMap = { MessageContract };

  const txTemplate = await getTransactionCode({
    name: "set-message",
    addressMap,
  });
  console.log({ txTemplate });

  await emulator.stop();
};

main();

getScriptCode(name, addressMap)

Returns Cadence template from file with name in _basepath_/scripts folder

Arguments

NameTypeOptionalDescription
namestringname of the script template
addressMapAddressMapobject to use for address mapping of existing deployed contracts

Returns

TypeDescription
stringCadence template code for specified script

Usage

import path from "path";
import { init, emulator, getScriptCode } from "flow-js-testing";

const main = async () => {
  const basePath = path.resolve(__dirname, "../cadence");
  const port = 8080;

  await init(basePath, { port });
  await emulator.start(port);

  // Let's assume we need to import MessageContract
  await deployContractByName({ name: "MessageContract" });
  const MessageContract = await getContractAddress("MessageContract");
  const addressMap = { MessageContract };

  const scriptTemplate = await getScriptCode({
    name: "get-message",
    addressMap,
  });

  console.log({ scriptTemplate });
  await emulator.stop();
};

main();

Types

AddressMap

Object to use for address mapping of existing deployed contracts. Key shall be string and value shall be Address

Example

const addressMap = {
  Messanger: "0x01cf0e2f2f715450",
  Logger: "0x179b6b1cb6755e31",
};

Interaction

Interaction is a Promise or function returning a promise.

Example

const ix = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1337);
    });
  }, 500);
};