Skip to main content

Testing

Introduction

Envio comes with a built-in testing library to assist developers write tests for their indexer. This library is specifically crafted to mock database states as well as events and assert event handler logic.

The testing library is simply a series of helper functions that can be used to write tests, which means that any JavaScript-based testing framework can be used. In the examples below, we use Mocha.

Learn by doing

Tests are written in JavaScript, TypeScript and ReScript for the Greeter template and can be explored by following the steps below

Generate greeter template in TypeScript using Envio CLI

envio init template -l typescript -d greeter -t greeter -n greeter

Run tests

pnpm test

See the test/test.ts file to understand how the tests are written.

Getting Started

This page assumes you have a functioning indexer setup for which you intend to write tests.

Install preferred testing framework, for example Mocha

pnpm i mocha

Validate you're using Envio v0.0.26 or above

envio -V

Create a test folder and a test file test.js inside it

Add a test command to your package.json

"test": "mocha",

Make sure to run envio codegen when changes are made to the config or schema files to regenerate the testing library (TestHelpers file).

envio codegen

Writing tests

Test library design

The library is designed to allow developers to mock the database, mock events and assert different outcomes to the database from these events.

The mock database is immutable and each action returns a new instance of the mocked database. This makes it robust and easy to test against previous states of the database.

Example steps of what most tests will look like

  1. Initialize the mock database
  2. Create a mock event
  3. Process the mock event on the mock database
  4. Assert against the expected database state

API

The generated TestHelpers file exposes a few different functions:

> createMockDb

The ability to create an instance of the MockDb

const mockDbInitial = MockDb.createMockDb();

> set

The ability to set an entity of a MockDb

const updatedMockDb = mockDbInitial.entities.EntityName.set(entity);
Where EntityName is the entity defined in the Schema

> get

  1. Ability to get an entity from the MockDb
const entity = updatedMockDb.entities.EntityName.get(id);
Where EntityName is the entity defined in the Schema

> createMockEvent

The ability to create an instance of an event

const eventMock = ContractName.EventName.createMockEvent({ params });
Where `ContactName` is the name of the contract defined in the config
Where `EventName` is the name of the event being emitted
Where `params` is an object of the parameters emitted in the event

Users can also define specific event log details by using mockEventData object as a parameter inside params object. This is useful when you want to test against specific event log details like block number, transaction hash, etc.

Event log details that can be defined inside mockEventData object are:

  • blockNumber
  • blockTimestamp
  • blockHash
  • chainId
  • srcAddress
  • transactionHash
  • transactionIndex
  • logIndex

> processEvent

The ability to process an event on a mockDb

const updatedMockDbFromEvent = ContractName.EventName.processEvent({
event: eventMock,
mockDb: updatedMockDb,
});
Where `ContactName` is the name of the contract defined in the config
Where `EventName` is the name of the event being emitted

Assertions

In the examples we use NodeJS's built-in assert module but you can use other popular JavaScript based assertion libraries like chai or expect.

Examples

A NewGreeting event creates a User entity

it("A NewGreeting event creates a User entity", () => {
// Initializing the mock database
const mockDbInitial = MockDb.createMockDb();

// Initializing values for mock event
const userAddress = Addresses.defaultAddress;
const greeting = "Hi there";

// Creating a mock event
const mockNewGreetingEvent = Greeter.NewGreeting.createMockEvent({
greeting: greeting,
user: userAddress,
});

// Processing the mock event on the mock database
const updatedMockDb = Greeter.NewGreeting.processEvent({
event: mockNewGreetingEvent,
mockDb: mockDbInitial,
});

// Expected entity that should be created
const expectedUserEntity = {
id: userAddress,
latestGreeting: greeting,
numberOfGreetings: 1,
greetings: [greeting],
};

// Getting the entity from the mock database
const actualUserEntity = updatedMockDb.entities.User.get(userAddress);

// Asserting that the entity in the mock database is the same as the expected entity
assert.deepEqual(expectedUserEntity, actualUserEntity);
});

2 Greetings from the same users results in that user having a greeter count of 2

it("2 Greetings from the same users results in that user having a greeter count of 2", () => {
// Initializing the mock database
const mockDbInitial = MockDb.createMockDb();
// Initializing values for mock event
const userAddress = Addresses.defaultAddress;
const greeting = "Hi there";
const greetingAgain = "Oh hello again";

// Creating a mock event
const mockNewGreetingEvent = Greeter.NewGreeting.createMockEvent({
greeting: greeting,
user: userAddress,
});

// Creating a mock event
const mockNewGreetingEvent2 = Greeter.NewGreeting.createMockEvent({
greeting: greetingAgain,
user: userAddress,
});

// Processing the mock event on the mock database
const updatedMockDb = Greeter.NewGreeting.processEvent({
event: mockNewGreetingEvent,
mockDb: mockDbInitial,
});

// Processing the mock event on the updated mock database
const updatedMockDb2 = Greeter.NewGreeting.processEvent({
event: mockNewGreetingEvent2,
mockDb: updatedMockDb,
});

// Getting the entity from the mock database
const actualUserEntity = updatedMockDb2.entities.User.get(userAddress);

// Asserting that the field value of the entity in the mock database is the same as the expected field value
assert.equal(2, actualUserEntity?.numberOfGreetings);
});

Troubleshooting

The testing code is available in versions of Envio v0.0.26 and above.

Make sure to import relevant files and packages into your tests file, it might look something like this;

const assert = require("assert");
const { MockDb, Greeter } = require("../generated/src/TestHelpers.bs");
const { Addresses } = require("../generated/src/bindings/Ethers.bs");

Dev note: 📢 When working in ReScript make sure to update your bsconfig file to add the test folder as a source and add rescript-mocha as a bs-dependency

If you encounter any issues or have questions, please reach out to us on Discord