@iobroker/testing 5.2.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024-2025 AlCalzone <d.griesel@gmx.net>
3
+ Copyright (c) 2024-2026 AlCalzone <d.griesel@gmx.net>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -7,7 +7,7 @@ This repo provides utilities for testing of ioBroker adapters and other ioBroker
7
7
 
8
8
  The unit tests are realized using the following tools that are provided by this module:
9
9
 
10
- - A mock database which implements the most basic functionality of `ioBroker`'s Objects and States DB by operating on `Map` objects.
10
+ - A mock database that implements the most basic functionality of `ioBroker`'s Objects and States DB by operating on `Map` objects.
11
11
  - A mock `Adapter` that is connected to the mock database. It implements basic functionality of the real `Adapter` class, but only operates on the mock database.
12
12
 
13
13
  Predefined methods for both unit and integration tests are exported.
@@ -136,7 +136,7 @@ This method creates a mock database and a mock adapter. See below for a more det
136
136
  const asserts = utils.unit.createAsserts(database, adapter);
137
137
  ```
138
138
 
139
- These methods take a mock database and adapter and create a set of asserts for your tests. All IDs may either be a string, which is taken literally, or an array of strings which are concatenated with `"."`. If an ID is not fully qualified, the adapter namespace is prepended automatically.
139
+ These methods take a mock database and adapter and create a set of assertions for your tests. All IDs may either be a string, which is taken literally, or an array of strings which are concatenated with `"."`. If an ID is not fully qualified, the adapter namespace is prepended automatically.
140
140
 
141
141
  - `assertObjectExists(id: string | string[])` asserts that an object with the given ID exists in the database.
142
142
  - `assertStateExists(id: string | string[])` asserts that a state with the given ID exists in the database.
@@ -148,15 +148,76 @@ These methods take a mock database and adapter and create a set of asserts for y
148
148
 
149
149
  #### MockDatabase
150
150
 
151
- TODO
151
+ `MockDatabase` is a minimalistic reimplementation of ioBroker's Objects and States DB that operates purely on in-memory `Map`s. It is created for you by `createMocks()`, but you can also instantiate it directly:
152
+
153
+ ```ts
154
+ const { MockDatabase } = require("@iobroker/testing");
155
+ const database = new MockDatabase();
156
+ ```
157
+
158
+ Objects and states are stored under their fully qualified ID (e.g. `"adapter.0.some.state"`). Wherever a method comes in two overloads, you can either pass the full ID as a single string or pass the namespace and the rest of the ID separately (they are joined with `"."`). Pattern-based getters accept `ioBroker`-style wildcards (`*`).
159
+
160
+ Populating the database:
161
+
162
+ - `publishObject(obj: ioBroker.PartialObject)` adds or replaces an object. The object **must** have an `_id` and a `type`; missing `common`/`native` are filled from a default template.
163
+ - `publishObjects(...objects)` publishes multiple objects at once.
164
+ - `publishStateObjects(...objects)` / `publishChannelObjects(...objects)` / `publishDeviceObjects(...objects)` publish objects while forcing their `type` to `state` / `channel` / `device`.
165
+ - `publishState(id: string, state: Partial<ioBroker.State> | null | undefined)` sets a state (missing properties are filled from a default template). Passing `null`/`undefined` deletes it.
166
+ - `publishStates(states: Record<string, Partial<ioBroker.State> | null | undefined>)` sets multiple states at once.
167
+
168
+ Reading and querying:
169
+
170
+ - `hasObject(id)` / `hasObject(namespace, id)` returns whether an object exists.
171
+ - `getObject(id)` / `getObject(namespace, id)` returns an object or `undefined`.
172
+ - `getObjects(pattern, type?)` / `getObjects(namespace, pattern, type?)` returns all matching objects as a `Record<string, ioBroker.Object>`, optionally filtered by `type`.
173
+ - `hasState(id)` / `hasState(namespace, id)` returns whether a state exists.
174
+ - `getState(id)` / `getState(namespace, id)` returns a state or `undefined`.
175
+ - `getStates(pattern)` returns all matching states as a `Record<string, ioBroker.State>`.
176
+
177
+ Deleting and resetting:
178
+
179
+ - `deleteObject(objOrID)` removes an object (accepts the object or its ID).
180
+ - `deleteState(id)` removes a state.
181
+ - `clearObjects()` / `clearStates()` empty the respective store, `clear()` empties both. Call this between tests to start with a fresh database.
152
182
 
153
183
  #### MockAdapter
154
184
 
155
- TODO
185
+ `MockAdapter` is a mock of ioBroker's `Adapter` class in which every method is replaced by a [Sinon stub](https://sinonjs.org/releases/latest/stubs/). It is created by `createMocks()` and is backed by a `MockDatabase`, so the DB-related methods actually read from and write to that database instead of a real DB.
186
+
187
+ ```ts
188
+ const { database, adapter } = utils.unit.createMocks();
189
+ // or with custom adapter options (name, instance, config, ...)
190
+ const { database, adapter } = utils.unit.createMocks({ name: "my-adapter" });
191
+ ```
192
+
193
+ Because the methods are Sinon stubs, you can assert on how your adapter code called them and override their behavior in individual tests:
194
+
195
+ ```ts
196
+ // Assert that a state was set
197
+ adapter.setState.callCount.should.equal(1);
198
+ adapter.setState.getCall(0).args[0].should.equal("info.connection");
199
+
200
+ // Override the return value / behavior for a single test
201
+ adapter.getForeignObjectAsync.resolves({ common: { name: "test" } });
202
+ ```
203
+
204
+ Key features:
205
+
206
+ - **Connected to the database:** methods like `getObject`, `setObject`, `setObjectNotExists`, `extendObject`, `getState`, `setState`, `setStateChanged`, `delState`, their `Foreign` variants and `getObjectView` / `getObjectList` are implemented to operate on the backing `MockDatabase`. IDs that are not fully qualified are automatically prefixed with the adapter namespace. The remaining methods are plain stubs that do nothing until you give them a behavior.
207
+ - **Promisified methods:** for every callback-style method, the corresponding `...Async` version (e.g. `getStateAsync`) is available, just like on a real adapter.
208
+ - **Event handlers:** the handlers your adapter registers (via `adapter.on(...)` or the adapter options) are captured and exposed so you can invoke them from your tests: `readyHandler`, `stateChangeHandler`, `objectChangeHandler`, `messageHandler` and `unloadHandler`. For example, call `await adapter.readyHandler()` to simulate the adapter's startup.
209
+ - **Logger:** `adapter.log` is a `MockLogger` whose `debug` / `info` / `warn` / `error` methods are stubs you can assert on.
210
+ - **`terminate()`** throws an error (carrying the exit reason) instead of exiting the process, so you can assert that your adapter tried to terminate.
211
+
212
+ Resetting the mock between tests:
213
+
214
+ - `resetMockHistory()` clears the recorded call history (call counts, arguments, ...) of all stubs and the logger, but keeps any custom behavior you configured.
215
+ - `resetMockBehavior()` restores the default behavior of all stubs, discarding your overrides.
216
+ - `resetMock()` does both.
156
217
 
157
218
  ### Example
158
219
 
159
- Here's an example how this can be used in a unit test:
220
+ Here's an example of how this can be used in a unit test:
160
221
 
161
222
  ```ts
162
223
  import { tests, utils } from "@iobroker/testing";
@@ -51,7 +51,7 @@ exports.getAdapterDependencies = getAdapterDependencies;
51
51
  const typeguards_1 = require("alcalzone-shared/typeguards");
52
52
  const debug_1 = __importDefault(require("debug"));
53
53
  const fs_extra_1 = require("fs-extra");
54
- const path = __importStar(require("path"));
54
+ const path = __importStar(require("node:path"));
55
55
  const debug = (0, debug_1.default)('testing:unit:adapterTools');
56
56
  /**
57
57
  * Loads an adapter's package.json
@@ -5,9 +5,9 @@ export interface ExecuteCommandOptions {
5
5
  cwd: string;
6
6
  /** Where to redirect the stdin. Default: process.stdin */
7
7
  stdin: NodeJS.ReadStream;
8
- /** A write stream to redirect the stdout, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stdout */
8
+ /** A writing stream to redirect the stdout, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stdout */
9
9
  stdout: NodeJS.WriteStream | 'pipe' | 'ignore';
10
- /** A write stream to redirect the stderr, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stderr */
10
+ /** A writing stream to redirect the stderr, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stderr */
11
11
  stderr: NodeJS.WriteStream | 'pipe' | 'ignore';
12
12
  }
13
13
  export interface ExecuteCommandResult {
@@ -19,6 +19,8 @@ export interface ExecuteCommandResult {
19
19
  stdout?: string;
20
20
  /** If options.stderr was set to "buffer", this contains the stderr of the spawned process */
21
21
  stderr?: string;
22
+ /** The error that prevented the process from being spawned, if it could not be started at all */
23
+ error?: Error;
22
24
  }
23
25
  export declare function executeCommand(command: string, options?: Partial<ExecuteCommandOptions>): Promise<ExecuteCommandResult>;
24
26
  /**
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeCommand = executeCommand;
4
- const child_process_1 = require("child_process");
4
+ const node_child_process_1 = require("node:child_process");
5
5
  const isWindows = /^win/.test(process.platform);
6
6
  /**
7
7
  * Executes a command and returns the exit code and (if requested) the stdout
@@ -55,7 +55,22 @@ function executeCommand(command, argsOrOptions, options) {
55
55
  try {
56
56
  let bufferedStdout;
57
57
  let bufferedStderr;
58
- const cmd = (0, child_process_1.spawn)(command, args, spawnOptions).on('close', (code, signal) => {
58
+ const cmd = (0, node_child_process_1.spawn)(command, args, spawnOptions)
59
+ .on('error', error => {
60
+ // The process could not be spawned at all - e.g. the command does not
61
+ // exist or the cwd is missing. Without a listener, Node treats this as
62
+ // an unhandled 'error' event and tears down the entire test process
63
+ // with a stack trace from node:internal, which tells the adapter
64
+ // developer nothing about the actual cause.
65
+ // Node emits 'close' after 'error' in this case; that second resolve
66
+ // is a no-op because the promise is already settled.
67
+ resolve({
68
+ error,
69
+ stdout: bufferedStdout,
70
+ stderr: bufferedStderr,
71
+ });
72
+ })
73
+ .on('close', (code, signal) => {
59
74
  resolve({
60
75
  exitCode: code ?? undefined,
61
76
  signal: signal ?? undefined,
@@ -83,8 +98,11 @@ function executeCommand(command, argsOrOptions, options) {
83
98
  });
84
99
  }
85
100
  }
86
- catch {
87
- // doesn't matter, we return the exit code in the "close" handler
101
+ catch (error) {
102
+ // `spawn` can also throw synchronously, e.g. on invalid arguments. There is no
103
+ // child process in that case, so neither 'close' nor 'error' will ever fire and
104
+ // the promise would stay pending forever.
105
+ resolve({ error: error });
88
106
  }
89
107
  });
90
108
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.str2regex = str2regex;
4
4
  function str2regex(pattern) {
5
5
  return new RegExp(pattern
6
- .replace(/\\/g, '\\\\') // Backslashes escapen
6
+ .replace(/\\/g, '\\\\') // Backslashes escape
7
7
  .replace(/\./g, '\\.') // Punkte als solche matchen
8
8
  .replace(/\*/g, '.*') // Wildcard in Regex umsetzen
9
9
  .replace(/!/g, '?!'));
@@ -6,7 +6,9 @@ export interface TestAdapterOptions {
6
6
  /** How long to wait before the adapter startup is considered successful */
7
7
  waitBeforeStartupSuccess?: number;
8
8
  /**
9
- * Which JS-Controller version or dist-tag should be used for the tests. Default: dev
9
+ * Which JS-Controller version or dist-tag should be used for the tests.
10
+ * Default: `dev`, except on Node.js <= 20 where it defaults to `7.2.2`, the last
11
+ * js-controller version that still supports Node.js 18 and 20.
10
12
  * This should only be changed during active development.
11
13
  */
12
14
  controllerVersion?: string;
@@ -46,6 +46,8 @@ const executeCommand_1 = require("../../../lib/executeCommand");
46
46
  const tools_1 = require("./tools");
47
47
  const debug = (0, debug_1.default)('testing:integration:AdapterSetup');
48
48
  class AdapterSetup {
49
+ adapterDir;
50
+ testDir;
49
51
  constructor(adapterDir, testDir) {
50
52
  this.adapterDir = adapterDir;
51
53
  this.testDir = testDir;
@@ -61,6 +63,11 @@ class AdapterSetup {
61
63
  debug(` appName: ${this.appName}`);
62
64
  debug(` adapterName: ${this.adapterName}`);
63
65
  }
66
+ testAdapterDir;
67
+ adapterName;
68
+ adapterFullName;
69
+ appName;
70
+ testControllerDir;
64
71
  /**
65
72
  * Tests if the adapter is already installed in the test directory
66
73
  */
@@ -47,6 +47,8 @@ const executeCommand_1 = require("../../../lib/executeCommand");
47
47
  const tools_1 = require("./tools");
48
48
  const debug = (0, debug_1.default)('testing:integration:ControllerSetup');
49
49
  class ControllerSetup {
50
+ adapterDir;
51
+ testDir;
50
52
  constructor(adapterDir, testDir) {
51
53
  this.adapterDir = adapterDir;
52
54
  this.testDir = testDir;
@@ -63,7 +65,19 @@ class ControllerSetup {
63
65
  debug(` appName: ${this.appName}`);
64
66
  debug(` adapterName: ${this.adapterName}`);
65
67
  }
66
- async prepareTestDir(controllerVersion = 'dev') {
68
+ appName;
69
+ adapterName;
70
+ testAdapterDir;
71
+ testControllerDir;
72
+ testDataDir;
73
+ async prepareTestDir(controllerVersion) {
74
+ const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
75
+ // js-controller 7.2.3 dropped support for Node.js 18 and 20. If no specific
76
+ // version was requested, and we are running on such a Node.js version, pin
77
+ // js-controller to the last version that still supports it.
78
+ if (!controllerVersion) {
79
+ controllerVersion = nodeMajorVersion <= 20 ? '7.2.2' : 'dev';
80
+ }
67
81
  debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`);
68
82
  // Make sure the test dir exists
69
83
  await (0, fs_extra_1.ensureDir)(this.testDir);
@@ -94,7 +108,6 @@ class ControllerSetup {
94
108
  await (0, fs_extra_1.unlink)(pckLockPath);
95
109
  }
96
110
  // Set the engineStrict flag on new Node.js versions to be in line with newer ioBroker installations
97
- const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
98
111
  if (nodeMajorVersion >= 10) {
99
112
  await (0, fs_extra_1.writeFile)(path.join(this.testDir, '.npmrc'), 'engine-strict=true', 'utf8');
100
113
  }
@@ -45,6 +45,9 @@ const tools_1 = require("./tools");
45
45
  const debug = (0, debug_1.default)('testing:integration:DBConnection');
46
46
  /** The DB connection capsules access to the states and objects DB */
47
47
  class DBConnection extends node_events_1.default {
48
+ appName;
49
+ testDir;
50
+ logger;
48
51
  /**
49
52
  * @param appName The branded name of "iobroker"
50
53
  * @param testDir The directory the integration tests are executed in
@@ -55,63 +58,20 @@ class DBConnection extends node_events_1.default {
55
58
  this.appName = appName;
56
59
  this.testDir = testDir;
57
60
  this.logger = logger;
58
- this._isRunning = false;
59
- this.getObject = id => {
60
- if (!this._objectsClient) {
61
- throw new Error('Objects DB is not running');
62
- }
63
- return this._objectsClient.getObjectAsync(id);
64
- };
65
- this.setObject = (...args) => {
66
- if (!this._objectsClient) {
67
- throw new Error('Objects DB is not running');
68
- }
69
- return this._objectsClient.setObjectAsync(...args);
70
- };
71
- this.delObject = (...args) => {
72
- if (!this._objectsClient) {
73
- throw new Error('Objects DB is not running');
74
- }
75
- return this._objectsClient.delObjectAsync(...args);
76
- };
77
- this.getState = (id) => {
78
- if (!this._statesClient) {
79
- throw new Error('States DB is not running');
80
- }
81
- return this._statesClient.getStateAsync(id);
82
- };
83
- this.setState = ((...args) => {
84
- if (!this._statesClient) {
85
- throw new Error('States DB is not running');
86
- }
87
- return this._statesClient.setStateAsync(...args);
88
- });
89
- this.delState = async (...args) => {
90
- if (!this._statesClient) {
91
- throw new Error('States DB is not running');
92
- }
93
- await new Promise((resolve, reject) => this._statesClient.delState(args[0], (err) => {
94
- if (err) {
95
- reject(err);
96
- }
97
- else {
98
- resolve();
99
- }
100
- }));
101
- };
102
- this.getObjectViewAsync = (...args) => {
103
- if (!this._objectsClient) {
104
- throw new Error('Objects DB is not running');
105
- }
106
- return this._objectsClient.getObjectViewAsync(...args);
107
- };
108
61
  this.testControllerDir = (0, tools_1.getTestControllerDir)(this.appName, testDir);
109
62
  this.testDataDir = (0, tools_1.getTestDataDir)(appName, testDir);
110
63
  }
64
+ testDataDir;
65
+ testControllerDir;
66
+ // TODO: These could use some better type definitions
67
+ _objectsServer;
68
+ _statesServer;
69
+ _objectsClient;
111
70
  /** The underlying objects client instance that can be used to access the objects DB */
112
71
  get objectsClient() {
113
72
  return this._objectsClient;
114
73
  }
74
+ _statesClient;
115
75
  /** The underlying states client instance that can be used to access the states DB */
116
76
  get statesClient() {
117
77
  return this._statesClient;
@@ -157,6 +117,7 @@ class DBConnection extends node_events_1.default {
157
117
  const systemFilename = path.join(this.testDataDir, `${this.appName}.json`);
158
118
  (0, fs_extra_1.writeJSONSync)(systemFilename, systemConfig, { spaces: 2 });
159
119
  }
120
+ _isRunning = false;
160
121
  get isRunning() {
161
122
  return this._isRunning;
162
123
  }
@@ -277,6 +238,49 @@ class DBConnection extends node_events_1.default {
277
238
  });
278
239
  debug(' => done!');
279
240
  }
241
+ getObject = id => {
242
+ if (!this._objectsClient) {
243
+ throw new Error('Objects DB is not running');
244
+ }
245
+ return this._objectsClient.getObjectAsync(id);
246
+ };
247
+ setObject = (...args) => {
248
+ if (!this._objectsClient) {
249
+ throw new Error('Objects DB is not running');
250
+ }
251
+ return this._objectsClient.setObjectAsync(...args);
252
+ };
253
+ delObject = (...args) => {
254
+ if (!this._objectsClient) {
255
+ throw new Error('Objects DB is not running');
256
+ }
257
+ return this._objectsClient.delObjectAsync(...args);
258
+ };
259
+ getState = (id) => {
260
+ if (!this._statesClient) {
261
+ throw new Error('States DB is not running');
262
+ }
263
+ return this._statesClient.getStateAsync(id);
264
+ };
265
+ setState = ((...args) => {
266
+ if (!this._statesClient) {
267
+ throw new Error('States DB is not running');
268
+ }
269
+ return this._statesClient.setStateAsync(...args);
270
+ });
271
+ delState = async (...args) => {
272
+ if (!this._statesClient) {
273
+ throw new Error('States DB is not running');
274
+ }
275
+ await new Promise((resolve, reject) => this._statesClient.delState(args[0], (err) => {
276
+ if (err) {
277
+ reject(err);
278
+ }
279
+ else {
280
+ resolve();
281
+ }
282
+ }));
283
+ };
280
284
  subscribeMessage(id) {
281
285
  if (!this._statesClient) {
282
286
  throw new Error('States DB is not running');
@@ -289,6 +293,12 @@ class DBConnection extends node_events_1.default {
289
293
  }
290
294
  this._statesClient.pushMessage(instanceId, msg, callback);
291
295
  }
296
+ getObjectViewAsync = (...args) => {
297
+ if (!this._objectsClient) {
298
+ throw new Error('Objects DB is not running');
299
+ }
300
+ return this._objectsClient.getObjectViewAsync(...args);
301
+ };
292
302
  getStateIDs(pattern = '*') {
293
303
  if (!this._statesClient) {
294
304
  throw new Error('States DB is not running');
@@ -53,6 +53,9 @@ const fromAdapterID = 'system.adapter.test.0';
53
53
  * Use it in every test to start a fresh adapter instance
54
54
  */
55
55
  class TestHarness extends node_events_1.EventEmitter {
56
+ adapterDir;
57
+ testDir;
58
+ dbConnection;
56
59
  /**
57
60
  * @param adapterDir The root directory of the adapter
58
61
  * @param testDir The directory the integration tests are executed in
@@ -62,7 +65,6 @@ class TestHarness extends node_events_1.EventEmitter {
62
65
  this.adapterDir = adapterDir;
63
66
  this.testDir = testDir;
64
67
  this.dbConnection = dbConnection;
65
- this.sendToID = 1;
66
68
  debug('Creating instance');
67
69
  this.adapterName = (0, adapterTools_1.getAdapterName)(this.adapterDir);
68
70
  this.appName = (0, adapterTools_1.getAppName)(adapterDir);
@@ -80,6 +82,10 @@ class TestHarness extends node_events_1.EventEmitter {
80
82
  this.emit('stateChange', id, state);
81
83
  });
82
84
  }
85
+ adapterName;
86
+ appName;
87
+ testControllerDir;
88
+ testAdapterDir;
83
89
  /** Gives direct access to the Objects DB */
84
90
  get objects() {
85
91
  if (!this.dbConnection.objectsClient) {
@@ -94,10 +100,12 @@ class TestHarness extends node_events_1.EventEmitter {
94
100
  }
95
101
  return this.dbConnection.statesClient;
96
102
  }
103
+ _adapterProcess;
97
104
  /** The process the adapter is running in */
98
105
  get adapterProcess() {
99
106
  return this._adapterProcess;
100
107
  }
108
+ _adapterExit;
101
109
  /** Contains the adapter exit code or signal if it was terminated unexpectedly */
102
110
  get adapterExit() {
103
111
  return this._adapterExit;
@@ -266,6 +274,7 @@ class TestHarness extends node_events_1.EventEmitter {
266
274
  });
267
275
  this.dbConnection.subscribeMessage(fromAdapterID);
268
276
  }
277
+ sendToID = 1;
269
278
  /** Sends a message to an adapter instance */
270
279
  sendTo(target, command, message, callback) {
271
280
  const stateChangedHandler = (id, state) => {
@@ -166,6 +166,12 @@ function validatePackageFiles(adapterDir) {
166
166
  }
167
167
  }
168
168
  });
169
+ it('No "prepare" script is defined in package.json', () => {
170
+ if ((0, typeguards_1.isObject)(packageContent.scripts) && 'prepare' in packageContent.scripts) {
171
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
172
+ throw new chai_1.AssertionError(`The "prepare" script must not be defined in the "scripts" section of package.json, found "${packageContent.scripts.prepare}"! It runs on every "npm install" of the adapter (including for end users) and can break installations.`);
173
+ }
174
+ });
169
175
  it('iobroker.js-controller is not listed as a dependency', () => {
170
176
  for (const depType of [
171
177
  'dependencies',
@@ -17,10 +17,8 @@ const stateTemplate = Object.freeze({
17
17
  * A minimalistic version of ioBroker's Objects and States DB that just operates on a Map
18
18
  */
19
19
  class MockDatabase {
20
- constructor() {
21
- this.objects = new Map();
22
- this.states = new Map();
23
- }
20
+ objects = new Map();
21
+ states = new Map();
24
22
  clearObjects() {
25
23
  this.objects.clear();
26
24
  }
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@iobroker/testing",
3
- "version": "5.2.2",
3
+ "version": "6.0.0",
4
4
  "description": "Shared utilities for adapter and module testing in ioBroker",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
7
  "files": [
8
8
  "build/"
9
9
  ],
10
+ "engines": {
11
+ "node": ">=22.19.0"
12
+ },
10
13
  "scripts": {
11
14
  "test": "mocha \"src/**/*.test.ts\"",
12
15
  "test:watch": "mocha \"src/**/*.test.ts\" --watch",
@@ -39,35 +42,35 @@
39
42
  },
40
43
  "homepage": "https://github.com/AlCalzone/testing#readme",
41
44
  "devDependencies": {
42
- "@alcalzone/release-script": "^5.0.0",
43
- "@alcalzone/release-script-plugin-license": "^4.0.0",
44
- "@iobroker/adapter-core": "^3.3.2",
45
- "@iobroker/eslint-config": "^2.2.0",
46
- "@iobroker/types": "^7.0.7",
47
- "@tsconfig/node16": "^16.1.6",
48
- "@types/debug": "^4.1.12",
45
+ "@alcalzone/release-script": "^5.2.1",
46
+ "@alcalzone/release-script-plugin-license": "^5.2.2",
47
+ "@iobroker/adapter-core": "^3.4.3",
48
+ "@iobroker/eslint-config": "^2.3.4",
49
+ "@iobroker/types": "^7.2.2",
50
+ "@tsconfig/node18": "^18.2.7",
51
+ "@types/debug": "^4.1.13",
49
52
  "@types/fs-extra": "^11.0.4",
50
- "@types/node": "^24.6.1",
51
- "rimraf": "^6.1.0",
53
+ "@types/node": "^22.20.1",
54
+ "rimraf": "^6.1.3",
52
55
  "source-map-support": "^0.5.21",
53
56
  "ts-node": "^10.9.2",
54
- "typescript": "~5.9.3"
57
+ "typescript": "~6.0.3"
55
58
  },
56
59
  "dependencies": {
57
60
  "@alcalzone/esbuild-register": "^2.5.1-1",
58
61
  "@types/chai": "^4.3.20",
59
62
  "@types/chai-as-promised": "^7.1.8",
60
63
  "@types/mocha": "^10.0.10",
61
- "@types/sinon": "^17.0.4",
64
+ "@types/sinon": "^22.0.0",
62
65
  "@types/sinon-chai": "^3.2.12",
63
66
  "alcalzone-shared": "~5.0.0",
64
67
  "chai": "^4.5.0",
65
68
  "chai-as-promised": "^7.1.2",
66
69
  "debug": "^4.4.3",
67
- "fs-extra": "^11.3.2",
70
+ "fs-extra": "^11.4.0",
68
71
  "json5": "^2.2.3",
69
- "mocha": "^11.7.3",
70
- "sinon": "^21.0.0",
72
+ "mocha": "^12.0.0",
73
+ "sinon": "^22.1.0",
71
74
  "sinon-chai": "^3.7.0"
72
75
  }
73
76
  }