Mocking Globals

Contents

This chapter introduces the concept of mocking.

Introduction

Mocking is the concept of creating fake objects that stand in for real objects in the system. This way, you may test or simulate bigger parts of a system - even without that system being actually available.

Globals

When programming in Javascript there is always a global scope. Depending on which Javascript engine/environment you are in, the global scope is different.

Useful reading: Global object

Browser

In the browser, the global scope is the window object. The window object contains the document object that represents the current web page. The document object contains functions and properties that allow you to inspect and manipulate the web page.

XP Framework

In the Enonic XP Framework the global scope contains a bunch of objects and functions that are useful when developing applications.

You can read about them here.

Source code

Let’s start by adding some more code we can test:

src/main/resources/lib/myproject/getAppConfig.ts
export const getAppConfig = () => {
    log.debug('getAppConfig() called');
    const config = app.config;
    log.info('App config:%s', config);
    return config;
};

Test without mock

Then, we write a simple test for the function above:

src/jest/server/getAppConfig.ts
import {
    describe,
    expect,
    test as it
} from '@jest/globals';
import { getAppConfig } from '/lib/myproject/getAppConfig';


describe('getAppConfig', () => {
    it('should return the application config', () => {
        expect(getAppConfig()).toEqual({});
    });
});

If you try to run this test without mocking some props on the global object, it will fail with the following error message:

ReferenceError: log is not defined
Since the starter-ts project template we used already have mocked the Enonic XP globals for us: you won’t get that error.

Test with mock

All the configuration below is already set up for us by the starter-ts project template we used. 🎉 But here we explain the reasoning behind it.

To avoid errors about globals not being defined, we have to mock the missing globals. In this example both the log and app objects must be mocked.

This can be achieved in multiple ways. When it comes to static values like the global app object, the following can be added to the serverSideConfig within your jest.config.ts file:

jest.config.ts - serverSideConfig
globals: {
    app: {
        name: 'com.example.myproject',
        config: {},
        version: '1.0.0'
    },
},

A more flexible solution, that also let you mock functions, like log.info is to use a setup file. It will run before the execution of each test file.

Example setup file:

src/jest/server/setupFile.ts
import type {App, Log} from './global.d';


// Avoid type errors
declare module globalThis {
    var app: App
    var log: Log
}


// In order for console to exist in the global scope when running tests in
// testEnvironment: 'node' the @types/node package must be installed and
// potentially listed under types in tsconfig.json.
globalThis.log = {
    debug: console.debug,
    info: console.info,
    error: console.error,
    warning: console.warn
}

Then, it must be registered it in the serverSideConfig:

jest.config.ts - serverSideConfig
setupFiles: ['<rootDir>/src/jest/server/setupFile.ts'],

This will mock default values for the global app and log objects, and the values can be augmented in the test files.

Types

To avoid type errors when accessing global objects and functions in the test files, you can import types directly from the src/jest/server/global.d.ts file, which is already set up for us by the starter-ts project template we used.

src/jest/server/global.d.ts
/// <reference types="@enonic-types/global" />

export declare type App = typeof app;
export declare type Log = typeof log;
export declare type DoubleUnderscore = typeof __;
export declare type Require = typeof require;
export declare type Resolve = typeof resolve;
src/jest/server/getAppConfig.test.ts
import type {App, Log} from './global.d';

import {
    beforeAll,
    describe,
    expect,
    test as it
} from '@jest/globals';


// Avoid type errors below.
declare module globalThis {
    var app: App
    var log: Log
}


// Augment the app.config object for tests in this file only.
globalThis.app.config.key = 'value';


describe('getAppConfig', () => {
    beforeAll(() => {
        // Silence log.debug for tests under this describe.
        globalThis.log.debug = () => {};
    });
    it('should return the application config', () => {
        import('/lib/myproject/getAppConfig').then(({getAppConfig}) => {
            expect(getAppConfig()).toEqual({
                key: 'value'
            });
        });
    });
});

Summary

In this chapter you learned how to mock global objects in the Enonic XP Framework when writing tests with Jest.

Let’s move on to the next chapter and learn how to mock functions and write our first "real" test for Enonic server-side code.


Contents

Contents