jcollum
jcollum

Reputation: 46579

How do I override config values at runtime with node-config?

I'd like to override some values at test-time, specifically setting my retries for an http service to 1 (immediate failure, no retries). Our project uses node-config. According to the docs I can override with NODE_CONFIG env variable:

node myapp.js --NODE_CONFIG='{"Customer":{"dbConfig":{"host":"customerdb.prod"}}}'

Well I would prefer to do this in my test, but not for all tests. The code says that you can allow config mutations by setting ALLOW_CONFIG_MUTATIONS.

process.env.ALLOW_CONFIG_MUTATIONS = "true";
const importFresh = require('import-fresh');
importFresh("config");

process.env.NODE_CONFIG = JSON.stringify({httpServices:{integration:{enrich: {retryInterval: 1, retries: 1}}}});
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.exist();
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.match(/retryInterval/);
expect(process.env.ALLOW_CONFIG_MUTATIONS, 'ALLOW_CONFIG_MUTATIONS not set').to.equal("true");

const testConfig = require("config");
console.dir(testConfig.get("httpServices.integration.enrich"));
expect(testConfig.get("httpServices.integration.enrich.retryInterval"), 'config value not set to 1').to.equal(1);

Result:

{ url: 'https://internal-**********',
  retryInterval: 5000,
  retries: 5 }
 `Error: config value not set to 1: Expected 5000 to equal specified value: 1`

How do I get this override to work?

(expect is from Hapi.js Code library)

Upvotes: 13

Views: 14291

Answers (5)

DLight
DLight

Reputation: 1825

You can override the config values

config.firstKey = 100;

but don't forget to set the values back to the original (e.g. in afterEach) to not impact other tests.

Example (without import-fresh):

// could also be in jest setup file, or in npm script (ALLOW_CONFIG_MUTATIONS=true jest)
process.env.ALLOW_CONFIG_MUTATIONS = 'true';

const config = require('config');

describe('...', () => {
  const originalFirstKey = config.get('firstKey');
  const originalSecondKey = config.get('secondKey');

  afterEach(() => {
    config.firstKey = originalFirstKey;
    config.secondKey = originalSecondKey;
  });

  it('test with new values', () => {
    config.firstKey = 100;
    config.secondKey = '* * * * *';

    console.log('config.get firstKey', config.get('firstKey'));
    console.log('config.get secondKey', config.get('secondKey'));
  });

  it('test with original values', () => {
    console.log('config.get firstKey', config.get('firstKey'));
    console.log('config.get secondKey', config.get('secondKey'));
  });
});

Note that ALLOW_CONFIG_MUTATIONS is needed here because we access the config values before overriding them.

Upvotes: 0

guy mograbi
guy mograbi

Reputation: 28608

Joining late, but other answers did not fit with the testing standard in my project, so here is what I came up with

TL;DR

Use mocks..

Detailed Answer

node-config uses a function get to get the configuration values. By mocking the function get you can easily modify any configuration you see fit..

My personal favorite library is sinon

Here is an implementation of a mock with sinon

const config = require('config');
const sinon = require('sinon');
class MockConfig {
    constructor () {
       this.params = {};
       this.sandbox = sinon.sandbox.create();
    }
    withConfValue (confKey, confValue) {
        this.params.confValues[confKey] = confValue;
        return this;
    }
    reset () {
        this.params.confValues: {};
        return this;
    }
    restore() {
       this.sandbox.restore();
    }
    apply () {
        this.restore(); // avoid duplicate wrapping
        this.sandbox.stub(config, 'get').callsFake((configKey) => {
            if (this.params.confValues.hasOwnProperty(configKey)) {
                return this.params.confValues[configKey];
            }
            // not ideal.. however `wrappedMethod` approach did not work for me
            // https://stackoverflow.com/a/57017971/1068746
            return configKey
               .split('.')
               .reduce((result, item) => result[item], config)
            
        });
    }
}

const instance = new MockConfig();
MockConfig.instance = () => instance;

module.exports = MockConfig;

Usage would be

const mockConfig = require('./mock_config').instance();

...
beforeEach(function () {
    mockConfig.reset().apply();
})

afterEach(function () {
     mockConfig.reset().clear();
})


it('should do something') {
    mockConfig.withConfValue('some_topic.some_field.property', someValue);
    ... rest of the test ... 
}

Assumptions

The only assumption this approach makes is that you adhere to node-config way of reading the configuration (using the get function) and not bypass it by accessing fields directly.

Upvotes: 1

deorst
deorst

Reputation: 317

Simply changing config's property worked for me. For example:

const config = require( 'config' ); 

config.httpServices.integration.enrich.retryInterval = 1;

// Do your tests...

UPD: Make sure that overrides are done before anyone calls the first config.get(), because the config object is made immutable as soon as any client uses the values via get().

Upvotes: 4

Mark Stosberg
Mark Stosberg

Reputation: 13381

I'm one of the maintainers of node-config. Your bug is that you used require the second time when you should have used importFresh again.

Your first use of "importFresh()" does nothing different than require() would, because it is the first use of require().

After setting some variables, you call require(), which will return the copy of config already generated and cached, ignoring the effects of the environment variables set.

You only needed to use importFresh() once, where you currently use require(). This will cause a "fresh" copy of the config object to be returned, as you expected.

Upvotes: 8

Stan Sarr
Stan Sarr

Reputation: 243

It's better to create a development.json, production.json et test.json in your config folder node-config will use it your app configuration. you just net to set your NODE_ENV to use the specific file. Hope it helps :)

Upvotes: 0

Related Questions