DEV Community

Max Daunarovich for Flow Blockchain

Posted on • Updated on

Build on Flow: JS Testing - 4. Storage Inspection

Intro

In the previous episode 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:

npx @onflow/flow-js-testing make storage-inspection
Enter fullscreen mode Exit fullscreen mode

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

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);
});
Enter fullscreen mode Exit fullscreen mode

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:

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);
});
Enter fullscreen mode Exit fullscreen mode

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

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);
});
Enter fullscreen mode Exit fullscreen mode

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:

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

  expect(capacity).toBe(100000);
  expect(used > 0).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

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

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

  await shallHavePath(Alice, "/public/flowTokenBalance");
  await shallHavePath(Alice, "/public/flowTokenReceiver");
});
Enter fullscreen mode Exit fullscreen mode

shallHaveStorageValue(account, {props})

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

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",
  });
});
Enter fullscreen mode Exit fullscreen mode

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:

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",
  });
});
Enter fullscreen mode Exit fullscreen mode

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

Good luck and have fun! 👋

Top comments (0)