Build on Flow: JS Testing - 2. Setup Emulator and Deploy Contracts

maxstalker

Max Daunarovich

Posted on May 26, 2023

Build on Flow: JS Testing - 2. Setup Emulator and Deploy Contracts

Intro

Last Time we've covered how you can get started with testing in your project. That article covers use of init and make commands in JS Testing Framework and how to use them to scaffold your testing enviroment.

We will assume you've gone through that process and have ready setup.

This time we will cover how you can deploy your newly developed contract to emulator to be able to run scripts and transactions against it.

Prerequisites

You will need to install Flow CLI in order to be able to spin up Flow Emulator. Follow instructions for your OS on Developer Portal.

If you haven't prepared your environment - follow instructions from previous article.

Setup Cadence Folder

Most of the methods will allow you to use Cadence code straight from the .cdc files. The only requirement is to use specific folder structure in designated folder.

Let's create cadence folder in the root of your project. Then create 3 new folders inside of it:

  • contracts - will hold all the contracts you would want to deploy
  • transactions - for any transactions code
  • scripts - same for scripts

Please note that folders should be called exactly in the same manner or they won't work. We have planned the task, which would allow you to use custom names, but it's not implemented yet

In order for framework to know what location you want to use you will need to call init method:

import path from "path";
import { emulator, init } from "@onflow/flow-js-testing";

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

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

Setup Emulator

In order to test any network interactions, we need instance of emulator running. Frameworks handle the process with emulator instance, which exposes two methods - start and stop. Those will handle running flow emulator process with necessary flags and extra steps after emulator will be stopped.

You can reference code above to see how we use it in beforeEach/afterEach blocks.

How to Deploy a contract?

Framework have two handy functions, which will help you deploy contracts:

  • deployContract - allows you to pass contract code as a string (handy, when you are just playing around)
  • deployContractByName- will allow you to deploy contract defined via file in your cadence folder (which you pass, when calling

They are quite similar in function, but deployContractByName provides you an easy way of deploying contracts located in your cadence/contracts folder. Let's create a file HelloWorld.cdc inside ouf our contracts folder and paste the following in:

pub contract HelloWorld{
    pub let greetings: String

    init(){
        self.greetings = "Hello, from Cadence"
    }
}
Enter fullscreen mode Exit fullscreen mode

Our little contract only have single field, which we can read in the future. Let me show you how we can test it.

Create new test suit with make command:

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

This command will run make tool provided by testing framework and create a first.js file, which will have a basic setup for you. Since deploying contract on Flow is just sending specifically designed transaction, we will use shallPass method to ensure everything works:

import path from "path";
import { init, emulator, shallPass, deployContractByName } from "@onflow/flow-js-testing";

// Increase timeout if your tests failing due to timeout
jest.setTimeout(10000);

describe("deploy", () => {
  beforeEach(async () => {
    const basePath = path.resolve(__dirname, "../cadence");
    const logging = false;

    await init(basePath);
    return emulator.start({ logging });
  });

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

  // We will put next block here
  // ========>
});
Enter fullscreen mode Exit fullscreen mode

Our first test will be super simple. We will wrap deployContractByName with shallPass and await the result of that computation:

test("deploy Hello", async () => {
  await shallPass(deployContractByName({ name: "Hello" }));
});
Enter fullscreen mode Exit fullscreen mode

Well done! 👍 But scenarios like that is never the case. Contracts are usually much more complicated and accept multiple arguments to init it's field. Let's create another contract Message.cdc in our contracts folder and populate it with following Cadence code:

pub contract Message{
    pub let message: String

    init(message: String){
        self.message = message
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to pass arguments, when we deploy contract. We can do this by specifying args field on the object we pass into deployContractByName. It should be an array of values in the same order we defined them on init method of our contract.

test("deploy Message", async () => {
  const message = "Cadence is great!";

  await shallPass(
    deployContractByName({
      name: "Message",
      args: [message],
    })
  );
});
Enter fullscreen mode Exit fullscreen mode

Note that framework will handle all the type conversions for you! 😎

You can try to add another argument to ensure that test will fail, ensuring you are doing
everything correctly.

Another function we have at our disposal is deployContract. It has pretty much the same API, except it expects code value for your Cadence instead of a file name. Let's make a simple test to
illustrate how you can use it.

test("deploy Custom", async () => {
  await shallPass(
    deployContract({
      code: `
          pub contract Custom{
            pub let message: String

            init(){
              self.message = "This is custom contract! Neat, ha? :)"
            }
          }
        `,
    })
  );
});
Enter fullscreen mode Exit fullscreen mode

You can pass arguments in the same fashion we did above for deployContractByName by specifying args field.

Originally the purpose of this function was to process and consume arguments from
deployContractByName and wasn't exported. After some consideration we've exported it to enable
people who just learning Cadence to experiment quikly

You can find detailed explanation of both functions on [Flow Developer Portal - Contract Management (https://developers.flow.com/tooling/flow-js-testing/contracts)

Too slow!

Sometimes scenario that you are trying to test takes more time than default Jest timeout - 5 seconds. You can control this by increasing the timeout to next reasonable value:

// Place this after import lines
import path from "path";
import { deployContractByName, emulator, executeScript, init } from "@onflow/flow-js-testing";

jest.setTimeout(20000);

// the rest of the test setup below 👇
Enter fullscreen mode Exit fullscreen mode

Some CI environments might be slower than you local machine, so if you see randomly failing tests - try to increase this value.

That's all, folks! Next time we will take a look how we can execute scripts, sign and send
transactions.

💖 💪 🙅 🚩
maxstalker
Max Daunarovich

Posted on May 26, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related