Build on Flow: JS Testing - 3. Execute Scripts and Send Transactions
Tags: testing, javascript, flow, blockchain
Published: 2023-05-26 06:21:32

## Intro

Now deploying contracts is all fun and games, but it becomes boring pretty fast, unless you can interact with them :)

There are two ways how you can interact with contracts and accounts on Flow - you can `read` data (via `scripts`) and you can `write` data (via transactions).

The biggest difference is — you can't mutate the state of the chain when executing scripts. Even if you called the contract's method, which implementation modifies the state, it won't be preserved when a script returns.

Let's start with non-intrusive interactions - scripts.

## Prerequisites

Let's assume we are using the same setup from our last part, and we already have our `cadence` folder. Later we will plant some scripts into `scripts` folder.

Create a new test suit file

```shell
npx @onflow/flow-js-testing make interaction
```

## Calculator

Since Cadence is able to do basic math operations, let's try to sum two numbers. Import `executeScript` and add new test inside `describe` block:

```js
test("calculator", async () => {
  const [result] = await executeScript({
    code: `
        pub fun main(a: Int, b: Int): Int{
          return a + b
        }
      `,
    args: ["10", "32"],
  });
  expect(result).toBe("42");
});
```

> Notice that we are passing numbers as strings here and the final result returned from the script will also be a string. You can add necessary conversions at your leisure, but this is how network and libraries process numbers on Flow

`executeScript` will return you a tuple (an array with 2 values) `[result, error]`, which you can
use in your assertions.

## Account Management

The Framework provides you a clean and easy way to operate accounts - `getAccountAddress` function.
This function will return an address, which you can pass into any kind of interaction and framework will do the rest for you. For example, you can use it as a transaction signer or one of the
arguments in a function:

```js
const Alice = await getAccountAddress("Alice");
```

> What is cool about this method is that this specific account is available anywhere inside your tests. So you can create a helper function, get Alice's account address one more time, and it will
> be resolved to exactly the same value! ✨

## Read Balance

Calculator is fun and games, but let's do something more practical. For example, we can read the FLOW balance of any account. It is pretty easy if you know you way around Cadence:

```js
test("read balance", async () => {
  const Alice = await getAccountAddress("Alice");
  const [balance] = await executeScript({
    code: `
        pub fun main(address: Address): UFix64 {
          let account = getAccount(address)
          return account.balance
        }
      `,
    args: [Alice],
  });

  expect(balance).toBe("0.00100000");
});
```

> All newly created accounts start with `0.001` amount of Flow, so we can assert this value and use it as baseline for future computations

## Contract Access

How about reading something from imported contract? Easy enough! Let's redeploy our `Message` contract and try to read `message` field from it:

```js
test("read message", async () => {
  const message = "noice";
  await shallResolve(deployContractByName({ name: "Message", args: [message] }));
  const [contractValue] = await executeScript({
    code: `
      import Message from 0x1
    
      pub fun main():String{
        return Message.message
      }
    `,
  });

  expect(contractValue).toBe(message);
});
```

> We can import contracts from any address - framework will resolve them and assign proper ones, before sending the script to the emulator.

## Short Notation

Let's shorten that script notation, by moving Cadence code into `read-message.cdc` file and place it into `scripts` folder. `executeScript` have a shorthand notion, where the first argument is a name of the file containing the script.

```js
test("read message - short", async () => {
  const message = "noice";
  await shallResolve(deployContractByName({ name: "Message", args: [message] }));
  const [contractValue] = await executeScript("read-message");

  expect(contractValue).toBe(message);
});
```

Arguments can be passed as second argument. Let's add `read-balance.cdc` file with code from above and rewrite our balance reading script:

```js
test("balance - short", async () => {
  const Alice = await getAccountAddress("Alice");
  const [balance] = await executeScript("read-balance", [Alice]);

  expect(balance).toBe("0.00100000");
});
```

Noice!

## Cadence Ninja Mutants 🐢

Let's bring some mutations to the mix! We will make two accounts, mint some tokens for one of them and transfer them to another account. This should be fun!

In order to mint FLOW, we will utilize `mintFlow` function, which expects the address and amount as arguments. Import `mintFlow` and `sendTransaction` at the top of the file

```js
test("FLOW transfer", async () => {
  const Alice = await getAccountAddress("Alice");
  const Bob = await getAccountAddress("Bob");

  await mintFlow(Alice, "42");

  const [aliceBalance] = await executeScript("read-balance", [Alice]);
  expect(aliceBalance).toBe("42.00100000");
});
```

Similar to how we've used `shallPass` to deploy contract, we can utilize it, when sending transactions.

```js
test("FLOW transfer", async () => {
  const Alice = await getAccountAddress("Alice");
  const Bob = await getAccountAddress("Bob");

  await mintFlow(Alice, "42");

  const [aliceBalance] = await executeScript("read-balance", [Alice]);
  expect(aliceBalance).toBe("42.00100000");

  await shallPass(
    sendTransaction({
      code: `
        import FungibleToken from 0x1
        
        transaction(receiverAddress: Address, amount: UFix64){
          prepare(sender: AuthAccount){
            let receiver = getAccount(receiverAddress)
            
            // Withdraw necessary amount into separate vault
            let senderVault <- sender
                .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!
                .withdraw(amount: amount)
            
            // Send to receiver
            getAccount(receiverAddress)
                .getCapability(/public/flowTokenReceiver)!
                .borrow<&{FungibleToken.Receiver}>()!
                .deposit(from: <- senderVault)
          }
        }
      `,
      args: [Bob, "1"],
      signers: [Alice],
    })
  );

  // Let's read updated balances and compare to expected values
  const [newAliceBalance] = await executeScript("read-balance", [Alice]);
  const [bobBalance] = await executeScript("read-balance", [Bob]);

  expect(newAliceBalance).toBe("41.00100000");
  expect(bobBalance).toBe("1.00100000");
});
```

## Send via Short Notation

Similar to how we've done it with scripts, `sendTransaction` function also supports short notation:

```js
sendTransaction(
  fileName, // file name in "transactions folder"
  [signers], // {optional) list of signers, even if that's single one
  [arguments] // (optional) list of arguments
);
```

Let's move all the Cadence code from example above into `send-flow.cdc` file under `cadence/transactions` folder and rewrite our test:

```js
test("FLOW transfer - short", async () => {
  const Alice = await getAccountAddress("Alice");
  const Bob = await getAccountAddress("Bob");

  await mintFlow(Alice, "42");

  const [aliceBalance] = await executeScript("read-balance", [Alice]);
  expect(aliceBalance).toBe("42.00100000");

  const signers = [Alice];

  const recipient = Bob;
  const amount = "1";
  const args = [recipient, amount];

  await shallPass(sendTransaction("send-flow", signers, args));

  // Let's read updated balances and compare to expected values
  const [newAliceBalance] = await executeScript("read-balance", [Alice]);
  const [bobBalance] = await executeScript("read-balance", [Bob]);

  expect(newAliceBalance).toBe("41.00100000");
  expect(bobBalance).toBe("1.00100000");
});
```

"Multisig" is supported out of the box, simply specify the expected number of signers in your transaction code and pass the corresponding number of addresses via `signers` parameter 😉

That's all, folks! [Next time](https://dev.to/maxstalker/build-on-flow-js-testing-4-storage-inspection-5e62), we will take a look at how we can inspect account storage and ensure resources were correctly minted or transferred. Until next time! 👋