From 5966ddda40e32d41747b4e9da77735f8ebcbea0d Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Thu, 6 Jun 2019 15:17:31 +0000 Subject: [PATCH] Add customisable userSettings userSettings can now be passed in on initialization via `helper.init(runtimepath, userSettings)` as well as during the runtime using `helper.settings(userSettings)`. This makes it easier for unit tests to emulate their production environment. Fixes #21 --- README.md | 24 +++++++++++++++++++++++- examples/function_spec.js | 22 ++++++++++++++++++++++ index.js | 26 +++++++++++++++++++++++++- test/settings_spec.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 test/settings_spec.js diff --git a/README.md b/README.md index 5f5e287..77e2aed 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,14 @@ We then have a set of mocha unit tests. These tests check that the node loads c To get started, we need to tell the helper where to find the node-red runtime. this is done by calling `helper.init(require.resolve('node-red'))` as shown. +The helper takes an optional `userSettings` parameter which is merged with the runtime defaults. + +```javascript +helper.init(require.resolve('node-red'), { + functionGlobalContext: { os:require('os') } +}); +``` + ## Getting nodes in the runtime The asynchronous `helper.load()` method calls the supplied callback function once the Node-RED server and runtime is ready. We can then call the `helper.getNode(id)` method to get a reference to nodes in the runtime. For more information on these methods see the API section below. @@ -320,6 +328,20 @@ Example: helper.request().post('/inject/invalid').expect(404).end(done); ``` +### settings(userSettings) + +Merges any userSettings with the defaults returned by `RED.settings`. Each invocation of this method will overwrite the previous userSettings to prevent unexpected problems in your tests. + +This will enable you to replicate your production environment within your tests, for example where you're using the `functionGlobalContext` to enable extra node modules within your functions. + +```javascript +// functions can now access os via global.get('os') +helper.settings({ functionGlobalContext: { os:require('os') } }); + +// reset back to defaults +helper.settings({ }); +``` + ### startServer(done) Starts a Node-RED server for testing nodes that depend on http or web sockets endpoints like the debug node. @@ -361,4 +383,4 @@ var logEvents = helper.log().args.filter(function(evt { npm run examples -This runs tests on an included lower-case node (as above) as well as snaphots of some of the core nodes' Javascript files to ensure the helper is working as expected. \ No newline at end of file +This runs tests on an included lower-case node (as above) as well as snaphots of some of the core nodes' Javascript files to ensure the helper is working as expected. diff --git a/examples/function_spec.js b/examples/function_spec.js index 1b817a1..bfa66fa 100644 --- a/examples/function_spec.js +++ b/examples/function_spec.js @@ -197,6 +197,28 @@ describe('function node', function() { }); }); + it('should access functionGlobalContext set via herlp settings()', function(done) { + var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload=global.get('foo');return msg;"}, + {id:"n2", type:"helper"}]; + helper.settings({ + functionGlobalContext: { + foo: (function() { + return 'bar'; + })(), + }, + }); + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', 'bar'); + done(); + }); + n1.receive({payload:"replaceme"}); + }); + helper.settings({}); + }); + function testNonObjectMessage(functionText,done) { var flow = [{id:"n1",type:"function",wires:[["n2"]],func:functionText}, {id:"n2", type:"helper"}]; diff --git a/index.js b/index.js index 2866d17..0e45085 100644 --- a/index.js +++ b/index.js @@ -128,11 +128,35 @@ class NodeTestHelper extends EventEmitter { } } - init(runtimePath) { + init(runtimePath, userSettings) { runtimePath = runtimePath || findRuntimePath(); if (runtimePath) { this._initRuntime(runtimePath); + if (userSettings) { + this.settings(userSettings); + } + } + } + + /** + * Merges any userSettings with the defaults returned by `RED.settings`. Each + * invocation of this method will overwrite the previous userSettings to prevent + * unexpected problems in your tests. + * + * This will enable you to replicate your production environment within your tests, + * for example where you're using the `functionGlobalContext` to enable extra node + * modules within your functions. + * @example + * helper.settings({ functionGlobalContext: { os:require('os') } }); + * @param {Object} userSettings - an object containing the runtime settings + * @return {Object} custom userSettings merged with default RED.settings + */ + settings(userSettings) { + if (userSettings) { + // to prevent unexpected problems, always merge with the default RED.settings + this._settings = Object.assign({}, this._RED.settings, userSettings); } + return this._settings; } load(testNode, testFlow, testCredentials, cb) { diff --git a/test/settings_spec.js b/test/settings_spec.js new file mode 100644 index 0000000..0668689 --- /dev/null +++ b/test/settings_spec.js @@ -0,0 +1,28 @@ +var should = require("should"); +var NodeTestHelper = require('../index.js').NodeTestHelper; + +var helper; +beforeEach(function() { + // .init() is implicitly called on instantiation so not required + helper = new NodeTestHelper(); +}); + +describe('add custom settings on init', function () { + it('should merge custom settings with RED.settings defaults', function () { + helper._settings.should.not.have.property('functionGlobalContext'); + helper.init(null, {functionGlobalContext: {}}); + helper._settings.should.have.property('functionGlobalContext'); + }); +}); + +describe('helper.settings() usage', function() { + it('should return a settings Object', function() { + var settings = helper.settings(); + should.exist(settings); + settings.should.have.property('get'); + }); + it('should not maintain settings state across multiple invocations', function() { + helper.settings({ foo: true }).should.have.property('foo'); + helper.settings({ bar: true }).should.not.have.property('foo'); + }); +});