Sandbox

The Sandbox is a new helper that ensures a unique environment for every test by generating a temporary network and crypto configuration. Doing this ensures that tests are more resilient by not relying on real production configurations which can change at any time.

The more important change with this is that you are working with a real instance of the application, not a cached instance that is shared through abusing the node module cache. This was a common pain point and could result in state being shared that you didn’t want to be shared. This was also the cause why tests had to rely so heavily on mocks because you wouldn’t have full access to the application and container.

Creating the Sandbox

The creation of the Sandbox for your tests is the first step. This will give you a new instance of the application without modifying or starting them. This means that you start with a clean slate for every test and need to create the necessary bindings yourself. The result of this is a minimal test setup and reducing the possible points of failure by not registering things that are not needed for a test.

1import { Sandbox } from "@arkecosystem/core-test-framework";
2 
3const sandbox: Sandbox = new Sandbox();

Now that you’ve created the Sandbox, you can access the application through the sandbox.app property. This is a bare-bones application without any bindings or plugins.

Core Configuration

After you’ve created the sandbox you have the ability to pass in a configuration for the Core instance. Usually you can skip this step but if you for example want to use your Bridgechain configuration you can do this before booting it.

1sandbox.withCoreOptions({
2 flags?: {
3 token: string;
4 network: string;
5 env?: string;
6 paths?: Paths;
7 };
8 plugins?: {
9 options?: Record<string, Record<string, any>>;
10 };
11 peers?: {};
12 delegates?: {};
13 environment?: {};
14 app?: {};
15});

Crypto Configuration

The same way you might need a custom Core configuration you might need a custom Crypto configuration. It’s as easy as for Core, simply pass in the configuration that should be used.

1sandbox.withCryptoOptions({
2 flags: {
3 network: string;
4 premine: string;
5 delegates: number;
6 blocktime: number;
7 maxTxPerBlock: number;
8 maxBlockPayload: number;
9 rewardHeight: number;
10 rewardAmount: number;
11 pubKeyHash: number;
12 wif: number;
13 token: string;
14 symbol: string;
15 explorer: string;
16 distribute: boolean;
17 };
18 exceptions?: Types.JsonObject;
19 genesisBlock?: Types.JsonObject;
20 milestones?: Types.JsonObject;
21 network?: Types.JsonObject;
22});

Information

If your tests don’t really on a specific configuration you can skip this step and let it generate a temporary configuration which will be removed after the tests have finished.

Booting the Sandbox

After the Sandbox has been created, you’ll need to boot it to finish the setup of the application. For that, you’ll use the boot method which exposes the app argument which will be the object you’ll interact with the most.

1await sandbox.boot(async ({ app }) => {
2 await app.bootstrap({
3 // Application configuration you wish to use.
4 });
5 
6 // Do anything you want to do before starting the application.
7 
8 await app.boot();
9});

After this has finished running you’ll have a full application instance with all bindings you would expect. You can work with it the same way you work with the application inside of plugins you develop.

Manually Registering Service Providers

When writes integration tests you will eventually run into an issue with code coverage collection. This happens because a Core instance is started from the dist folder where the JavaScript files reside.

In order to collect code coverage for your package you will need to manually register it from the src folder so that the coverage can be collected from the TypeScript files.

1await sandbox.boot(async ({ app }) => {
2 await app.bootstrap({
3 // Application configuration you wish to use.
4 });
5 
6 sandbox.registerServiceProvider({
7 name: "@arkecosystem/core-api",
8 path: resolve(__dirname, "../../../../packages/core-api"),
9 klass: ServiceProvider,
10 });
11 
12 await app.boot();
13});

Information

You will only need this for integration tests as functional tests to not produce any coverage and unit tests work directly with the TypeScript files which means code coverage will be generated.

Disposing the Sandbox

After your tests pass, you’ll obviously need to dispose of the sandbox to not pollute the local filesystem or have any relics that could interfere with future test runs. Disposing of the sandbox only requires a single line to remove all temporary files and terminate all connections and servers.

1await sandbox.dispose();
Last updated 2 years ago
Edit Page
Share: