Introduction

In the previous article, we've shown how the Core can be extended and how it has been reworked from the ground up to give developers more control over the way they want Core and other plugins to behave. Today, we’ll take a look at what improvements have been made to make it easier to maintain Core, what role testing plays in this and how it all has been improved.

Documentation

As Core 3.0 is a major milestone, we have also completely overhauled our documentation from scratch with a focus on usability and going into more detail about the internals and how to leverage them to achieve your goals.

The new documentation is split into several sections that each give an in-depth insight into different aspects of Core.

The architecture section will give you a closer look into how Core is structured with explanations into certain design choices. The services section will go into detail about how the internals of Core function, how they are split into smaller parts that are easier to test and the reasoning behind why they exist. The testing section will introduce two different concepts and types of tests, how they apply to Core and how you can get started with writing your own tests. The CLI section will teach you how to use the CLI, how to make plugins for it and the general idea and goals of it.

Type Doc

Another addition on top of our completely rewritten documentation is the use of Microsoft’s TSDoc combined with Type Doc to generate it.

The combination of both of these allows us to generate documentation for the API of Core. See an example of how documentation like that could look like at Type Doc API documentation.

The documentation that results out of this is targeted at our developer audience by providing more technical documentation without having to browse the code directly. We will always include the latest documentation for each package in the repository so that you can easily access it offline whenever needed so you don't have to rely on our online documentation.

Entity Factories

Entity Factories provide you with an easy way of generating different types of data. The way they work is that they have a default state which determines which data will always be present and generate it. This default state can be altered by registering other states with unique names that modify or add something to the data that was created by the default state.

import { FactoryBuilder } from "@packages/core-test-framework"; const builder: FactoryBuilder = new FactoryBuilder(); console.log(builder.get("Wallet").make());

Entity Factories are very flexible and will greatly reduce the overhead in your tests and ensure that you are generating new fixtures for every test, instead of relying on fixtures you store in your repository that risk becoming outdated. By default, we ship with a wide variety of factories to create blocks, identities, peers, rounds, transactions and wallets. That should cover most of the basic cases, and you can easily add states to add your own data to them.

Generators

Generators are in the same category as Entity Factories. They make it easy to generate configurations for either Core or Crypto sections. The Crypto configuration is composed of exceptions, milestones, network details and the genesis block.

import { Generators } from "@arkecosystem/core-test-framework"; // This will give you a new random core configuration just for tests.

Generators.generateCoreConfig(); // This will give you a new random crypto configuration just for tests.

Generators.generateCryptoConfig();

Generators will make it a lot easier to generate temporary configurations for your test suites and avoid having to maintain copies of the core and crypto configurations on your own.

Functional, Integration and Unit Testing

When Core 2.0 was initially started, it was a monolith developed with JavaScript and running its small test suite with Mocha. As the application grew, it moved to a modular architecture replacing JavaScript with TypeScript and moving to Jest to run its test suite.

All of those transitions bring us to today and how testing in Core 2.0 has been achieved and what improvements have been made in Core 3.0 to make it easier to test your code. The result of all those transitions was that everything testing related to Core 2.0 was very fragmented. You would find some test helpers in the unit folder, others in the functional folder and also some in actual NPM packages where you wouldn't expect them.

This fragmentation made it difficult to figure out exactly how to test your own code to ensure it is doing what you want it to do. In Core 3.0, we have completely reworked testing to make it easier to work with a real instance of Core without having to mock or stub the full application instance.

import { Sandbox } from "@arkecosystem/core-test-framework"; const sandbox: Sandbox = new Sandbox(); // Generate a core configuration with the given options.

sandbox.withCoreOptions({

flags: {

token: "ark",

network: "testnet",

env: "test",

},

peers: {

list: [{ ip: "127.0.0.1", port: 4000 }],

},

}); // Prepare the application

await sandbox.boot(async ({ app }) => {

await app.bootstrap({

flags: {

token: "ark",

network: "testnet",

env: "test",

processType: "core",

},

});

}); // Start the application

await app.boot();

We hope that these changes make it easier for developers to test their code and gain the confidence that they are implementing everything the right way. We'll continue to improve the testing framework and setup as more developers get their hands on it and provide feedback.

CLI Testing

Core 3.0 ships with a pluggable CLI and testing was one of the major reasons we replaced the old oclif-based CLI with our new implementation. Testing with oclif and Jest in combination was very tedious and would require a lot of mocking because we wouldn't have access to a lot of the internals of oclif and commands itself.

With our new CLI, we wanted to provide both a pleasant developer and testing experience. The first has been achieved by clearly separating all of the internals like I/O handling, environment access and the way arguments/flags are declared. All of them now have a small scope of responsibility which makes them easy to test and swap out which in turn makes testing as simplified as it could be as there’s no longer a necessity to constantly mock everything.

The new CLI gains the same benefits as Core itself when it comes to testing and ease of use due to the fact that both of them use an IoC-based architecture with Inversify.

import { Console } from "@arkecosystem/core-test-framework"; // This is the subject we will test.

import { Command } from "@packages/core/src/commands/env-get"; // Create a new fake console.

const cli = new Console(); // Create a mock to capture what is being executed.

let message: string;

jest.spyOn(console, "log").mockImplementationOnce(m => (message = m)); // Execute the test subject with the fake console.

await cli.withFlags({ key: "CORE_LOG_LEVEL" }).execute(Command); // Assert that something happened when the command was executed.

expect(message).toBe("emergency");

Implementing and testing your own CLI commands couldn't be easier and we're excited to see what functionality you'll be adding to Core.

What's next?

This concludes Part 5 of the Let’s Explore Core series. In the next and final part, we'll take a look at what, how and why some tooling has been changed or rethought and what the plans for Core 3.0 are before launching.

Let’s Explore Core Series

If you have missed other Let’s Explore Core series post, you can read them by following the links below: