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:
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:
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:
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:
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
:
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.
/// <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;
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.