Build on Flow: JS Testing - 4. Storage Inspection
Tags: testing, javascript, flow, blockchain
Published: 2023-05-26 06:21:53

## Intro

In the [previous episode](https://dev.to/maxstalker/build-on-flow-js-testing-3-execute-scripts-and-send-transactions-5hac) of our epic journey, we've discovered how we can interact with Flow Emulator by sending transactions and executing scripts. Today we will continue exploring available functions that help us build quick and easy to read tests for our Cadence contracts.

Reading simple values from contracts and accounts is a pretty easy task, even if you write all the Cadence code manually. But ensuring that account has proper paths setup or specific item in its collection could be tedious.

The Framework provides several methods to inspect account storage fast and painlessly.

Let's start by creating another test suit:

```shell
npx @onflow/flow-js-testing make storage-inspection
```

## Inspect Available Paths

To gather all available paths on accounts we will call `getPaths` function, which will return an object containing three Sets: 
- `publicPaths` 
- `privatePaths` 
- `storagePaths`

You can inspect them as you would do with any other Set.

Let's confirm that `Alice` has all the necessary bits to operate FLOW token (all new accounts do, but this will simplify the process).

```js
test("get paths", async () => {
  const Alice = await getAccountAddress("Alice");
  const { publicPaths, privatePaths, storagePaths } = await getPaths(Alice);

  // Newly created account shall have 2 public and 1 storage slot occupied for FLOW Vault
  expect(publicPaths.size).toBe(2);
  expect(publicPaths.has("flowTokenBalance")).toBe(true);
  expect(publicPaths.has("flowTokenReceiver")).toBe(true);

  // Newly created account doesn't have any private paths
  expect(privatePaths.size).toBe(0);

  // And it should have single storage path for FLOW token vault
  expect(storagePaths.size).toBe(1);
  expect(storagePaths.has("flowTokenVault")).toBe(true);
});
```

## Get Type Restrictions on Path

A common case is to ensure that account has specific capability in its account and ensure specific type restrictions on it to confirm that transactions would work correctly. We can get an extended version of paths with extra information about them via `getPathsWithType`:

```js
test("read path types", async () => {
  const { publicPaths } = await getPathsWithType("Alice");
  const refTokenBalance = publicPaths.flowTokenBalance;

  expect(refTokenBalance).not.toBe(undefined);
  expect(refTokenBalance.restrictionsList.has("A.ee82856bf20e2aa6.FungibleToken.Balance")).toBe(
    true
  );
  expect(refTokenBalance.restrictionsList.size).toBe(1);
  expect(refTokenBalance.haveRestrictions("FungibleToken.Balance")).toBe(true);
});
```

You can comment out those `expect` statements and use `console.log(refTokenBalance)` to see all available options you can use to assert it's correctness.

## Read Storage Values

Now let's store a value in Alice's storage and try to read it via `getStorageValue`. We need to pass account name (i.e. `"Alice"`) or address we resolved with `getAccountAddress` and storage path

```js
test("read storage", async () => {
  const Alice = await getAccountAddress("Alice");
  await shallPass(
    sendTransaction({
      code: `
          transaction{
            prepare(signer: AuthAccount){
              signer.save(42, to: /storage/answer)
            }
          }
        `,
      signers: [Alice],
    })
  );

  const { storagePaths } = await getPaths(Alice);
  const answer = await getStorageValue(Alice, "answer");
  expect(answer).toBe("42");
  expect(storagePaths.has("answer")).toBe(true);

  // We can also try to read non-existing value, which shall be resolved to null
  // Notice that we pass string here ;)
  const empty = await getStorageValue("Alice", "empty");
  expect(empty).toBe(null);
});
```

## Storage Stats

Another useful method you can emply during account inspection is `getStorageStats` - you might need one if you think users might hold huge collections and check how much of the storage it will occupy:

```js
test("Read storage stats", async () => {
  const { capacity, used } = await getStorageStats("Alice");

  expect(capacity).toBe(100000);
  expect(used > 0).toBe(true);
});
```

## Jest Helpers

Last, but not the least, there are a couple Jest helpers to simplify the inspection process and shorten your tests.

### `shallHavePath(account, pathName)`

This function will allow to quickly confirm that path exists on account. Note, that second argument contains a full path, including domain

```js
test("get paths", async () => {
  const Alice = await getAccountAddress("Alice");

  await shallHavePath(Alice, "/public/flowTokenBalance");
  await shallHavePath(Alice, "/public/flowTokenReceiver");
});
```

### `shallHaveStorageValue(account, {props})`

Using this function, we can quickly check storage value—both complex and simple ones. Let's start with simple:

```js
test("read storage", async () => {
  const Alice = await getAccountAddress("Alice");
  await shallPass(
    sendTransaction({
      code: `
          transaction{
            prepare(signer: AuthAccount){
              signer.save(42, to: /storage/answer)
            }
          }
        `,
      signers: [Alice],
    })
  );

  // This time we only need to path path in storage domain
  await shallHaveStorageValue(Alice, {
    pathName: "answer",
    expect: "42",
  });
});
```

Or if we know that value in storage is complex - let's say some NFT or Vault, then we can provide a key to how we can access it and check our expectations:

```js
test("compare complex storage value", async () => {
  const Alice = await getAccountAddress("Alice");

  // We will read "balance" field of the value stored in "/storage/flowTokenVault" slot
  await shallHaveStorageValue(Alice, {
    pathName: "flowTokenVault",
    key: "balance",
    expect: "0.00100000",
  });
});
```

This is it for today! 😎
Let us know if you think we've missed some valuable examples 😉

Good luck and have fun! 👋