UNPKG

@iobroker/testing

Version:

Shared utilities for adapter and module testing in ioBroker

206 lines (156 loc) 6.95 kB
# @iobroker/testing This repo provides utilities for testing of ioBroker adapters and other ioBroker-related modules. It supports: - **Unit tests** using mocks (without a running JS-Controller) **deprecated** and no longer used - **Integration tests** that test against a running JS-Controller instance. The unit tests are realized using the following tools that are provided by this module: - A mock database which implements the most basic functionality of `ioBroker`'s Objects and States DB by operating on `Map` objects. - 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. Predefined methods for both unit and integration tests are exported. ## Usage ### Validating package files (package.json, io-package.json, ...) ```ts const path = require("path"); const { tests } = require("@iobroker/testing"); // Run tests tests.packageFiles(path.join(__dirname, "..")); // ~~~~~~~~~~~~~~~~~~~~~~~~~ // This should be the adapter's root directory ``` ### Adapter startup (Integration test) Run the following snippet in a `mocha` test file to test the adapter startup process against a real JS-Controller instance: ```ts const path = require("path"); const { tests } = require("@iobroker/testing"); // Run tests tests.integration(path.join(__dirname, ".."), { // ~~~~~~~~~~~~~~~~~~~~~~~~~ // This should be the adapter's root directory // If the adapter may call process.exit during startup, define here which exit codes are allowed. // By default, termination during startup is not allowed. allowedExitCodes: [11], // To test against a different version of JS-Controller, you can change the version or dist-tag here. // Make sure to remove this setting when you're done testing. controllerVersion: "latest", // or a specific version like "4.0.1" // Define your own tests inside defineAdditionalTests defineAdditionalTests({ suite }) { // All tests (it, describe) must be grouped in one or more suites. Each suite sets up a fresh environment for the adapter tests. // At the beginning of each suite, the databases will be reset and the adapter will be started. // The adapter will run until the end of each suite. // Since the tests are heavily instrumented, each suite gives access to a so called "harness" to control the tests. suite("Test sendTo()", (getHarness) => { // For convenience, get the current suite's harness before all tests let harness; before(() => { harness = getHarness(); }); it("Should work", () => { return new Promise(async (resolve) => { // Start the adapter and wait until it has started await harness.startAdapterAndWait(); // Perform the actual test: harness.sendTo("adapter.0", "test", "message", (resp) => { console.dir(resp); resolve(); }); }); }); }); // While developing the tests, you can run only a single suite using `suite.only`... suite.only("Only this will run", (getHarness) => { // ... }); // ...or prevent a suite from running using `suite.skip`: suite.skip("This will never run", (getHarness) => { // ... }); }, }); ``` ### Adapter startup (Unit test) **Unit tests for adapter startup were removed and are essentially a no-op now.** If you defined your own tests, they should still work. ```ts const path = require("path"); const { tests } = require("@iobroker/testing"); tests.unit(path.join(__dirname, ".."), { // ~~~~~~~~~~~~~~~~~~~~~~~~~ // This should be the adapter's root directory // Define your own tests inside defineAdditionalTests. // If you need predefined objects etc. here, you need to take care of it yourself defineAdditionalTests() { it("works", () => { // see below how these could look like }); }, }); ``` ### Helper functions for your own tests Under `utils`, several functions are exposed to use in your own tests: ```ts const { utils } = require("@iobroker/testing"); ``` Currently, only `utils.unit` is defined which contains tools for unit tests: #### createMocks() ```ts const { database, adapter } = utils.unit.createMocks(); // or (with custom adapter options) const { database, adapter } = utils.unit.createMocks(adapterOptions); ``` This method creates a mock database and a mock adapter. See below for a more detailed description #### createAsserts() ```ts const asserts = utils.unit.createAsserts(database, adapter); ``` 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. - `assertObjectExists(id: string | string[])` asserts that an object with the given ID exists in the database. - `assertStateExists(id: string | string[])` asserts that a state with the given ID exists in the database. - `assertStateHasValue(id: string | string[], value: any)` asserts that a state has the given value. - `assertStateIsAcked(id: string | string[], ack: boolean = true)` asserts that a state is `ack`ed (or not if `ack === false`). - `assertStateProperty(id: string | string[], property: string, value: any)` asserts that one of the state's properties (e.g. `from`) has the given value - `assertObjectCommon(id: string | string[], common: ioBroker.ObjectCommon)` asserts that an object's common part includes the given `common` object. - `assertObjectNative(id: string | string[], native: object)` asserts that an object's native part includes the given `native` object. #### MockDatabase TODO #### MockAdapter TODO ### Example Here's an example how this can be used in a unit test: ```ts import { tests, utils } from "@iobroker/testing"; // Run tests tests.unit(path.join(__dirname, ".."), { // ~~~~~~~~~~~~~~~~~~~~~~~~~ // This should be the adapter's root directory // Define your own tests inside defineAdditionalTests defineAdditionalTests() { // Create mocks and asserts const { adapter, database } = utils.unit.createMocks(); const { assertObjectExists } = utils.unit.createAsserts( database, adapter, ); describe("my test", () => { afterEach(() => { // The mocks keep track of all method invocations - reset them after each single test adapter.resetMockHistory(); // We want to start each test with a fresh database database.clear(); }); it("works", () => { // Create an object in the fake db we will use in this test const theObject: ioBroker.PartialObject = { _id: "whatever", type: "state", common: { role: "whatever", }, }; mocks.database.publishObject(theObject); // Do something that should be tested // Assert that the object still exists assertObjectExists(theObject._id); }); }); }, }); ```