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)
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.
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().
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.
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 :)
Related
I am reading a code like below:
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import request from "supertest";
import { app } from "../app";
declare global {
function signin(): Promise<string[]>;
}
let mongo: any;
beforeAll(async () => {
process.env.JWT_KEY = "asdfasdf";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const mongo = await MongoMemoryServer.create();
const mongoUri = mongo.getUri();
await mongoose.connect(mongoUri, {});
});
beforeEach(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.deleteMany({});
}
});
afterAll(async () => {
if (mongo) {
await mongo.stop();
}
await mongoose.connection.close();
});
global.signin = async () => {
const email = "test#test.com";
const password = "password";
const response = await request(app)
.post("/api/users/signup")
.send({
email,
password,
})
.expect(201);
const cookie = response.get("Set-Cookie");
return cookie;
};
I can't understand the purpose of global.signin function and how does it work? I guess it has something to do with Jest but as long as I know the Jest codes should be inside the __test__ folder with the same file name and .test.ts extension. But the above function is defined and used inside the setup.ts file in the root of the application.
I also see some codes like following:
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
In some .ts files of the project as well that I am not sure are these global variables the same as the other globals I mentioned above or these are different things? I am interested to know how this global variables work as well?
The piece of code you shared is making use of global augmentation https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
// Hint typescript that your global object will have a custom signin function
declare global {
function signin(): Promise<string[]>;
}
// Assign value to global.signin
global.signin = async () => { /* implementation */ };
Likely one or multiple modules ("mongoose", "supertest", "../app") imported by the test file is using global.signin (or window.signin) at some point (or maybe one of their nested imports is => look for "signin(" in the project). Thus for testing purposes, global.signin needed to be mocked. However just adding global.signin = something would raise a typescript error, because signin is not a standard global variable. This is where declare global comes into play. It hints typescript that in your particular context, a signin function is expected to exist in global scope.
JavaScript/TypeScript running in node will try to resolve anything it can't find in the current local scope in global (the same way a browser would look in window). Any function or variable you can access globally (e.g. setTimeout()), can also be accessed with global. as prefix. It just makes it explicit.
What happens in your code are two things:
declare global {
function signin(): Promise<string[]>;
}
Here it tells typescript's type system that the global object also has a function called signin. This part is not required but it makes sense required for typescript to allow you to access / define that function, in JavaScript you simply define it.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html has some details how declare works.
global.signin = async () => {
// code ...
};
And here it is actually added to the global object.
In JavaScript non strict mode you could even write (notice the lack of var/let/const/global.)
signin = async () => {
// code ...
};
I don't see signin getting used anywhere in that code so the reason for it is unclear to me. As long as the file that defines it gets loaded you can call the function simply by referring to it as signin(). The global. is added implicitly.
The purpose of
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
is more practical, in express you may want to add properties to your requests that get added by middleware. By declaring that the Express Request has a property called currentUser you get to do
app.get((req, res) => {
const user: UserPayload = req.currentUser
...
})
without typescript complaining about an unknown property.
More on that for example https://blog.logrocket.com/extend-express-request-object-typescript/
I'm facing a problem I'm not able to resolve on my own, maybe some of you faced the same problem.
Let me show you what I'm trying to do, here is the mock:
let mockConfig = {name: 'dude'};
jest.mock('../../../configManager', () => mockConfig);
configManager is a dependency of the function I'm trying to test.
It works well but I want to change the returning object of configManager in another test so the tested function behaves differently.
Let me show you, here is the function I'm testing:
const config = require('../../../configManager');
module.exports = () => {
if (config.name === 'dude') {
do stuff;
}
if (config.name === 'dudette') {
do something else;
}
So, typically, I want to change the config.name to 'dudette' to be able to test the second part of my function.
Naturally, when I want to do this with an imported function, I just do:
let mockJsonQueryResult = { value: 'stuff' };
jest.mock('json-query', () => jest.fn(() => mockJsonQueryResult));
and then in the test, I directly set another value to mockJsonQueryResult:
mockJsonQueryResult = { value: 'hotterStuff' };
But I don't find any way of doing this with a dependency that returns an object, with a dependency returning a function, no problem.
Is there even any way of doing this?
Thanks in advance!
Edit: this is not the same as how to change jest mock function return value in each test? as #Dor Shinar suggested because his problem is to mock a function, even if it is inside a returning object it is still a function, I just want to change a value inside the returned object.
So, I found a solution I'm not completely satisfied with but it works:
I simply set the original full object and then for my tests, change the value of specific properties by setting them directly before calling the function I want to test.
example:
let mockConfig = { person: { name: 'dude', origin: {country: 'France'} } };
jest.mock('../../../configManager', () => mockConfig);
mockConfig.person = {};
mockConfig.person.name = 'dudette';
You don't need to mock the module at all.
If your module export is just an object with property values then just change the properties as needed.
Here is a simple working example to demonstrate:
configManager.js
module.exports = {
name: 'original'
}
code.js
const config = require('./configManager');
module.exports = () => `name: ${config.name}`;
code.test.js
const config = require('./configManager');
const func = require('./code');
test('func', () => {
expect(func()).toBe('name: original'); // Success!
config.name = 'dude';
expect(func()).toBe('name: dude'); // Success!
config.name = 'dudette';
expect(func()).toBe('name: dudette'); // Success!
})
Details
A module binding can't be directly changed to something else:
const config = require('./configManager');
config = { name: 'mock' }; // <= this doesn't work
...but you can change the properties of an object representing a module binding:
const config = require('./configManager');
config.name = 'mock'; // <= this works!
...and any code using the module will automatically see the changes.
My node app has a fairly large test suite that depends on ShouldJS module. The problem is that the library blocks assigning .should properties on objects, but my test suite needs to spin up a server that needs to be mockable (i.e. run in the same process as my test suite) and dynamically build ElasticSearch queries, that need to set .should property on Objects.
I tried to un-require ShouldJS using an approach described here. But that didn't help - see example script below. Is there a workaround or an alternative approach?
Example:
require('should');
var objBefore = {};
objBefore.should = 'should1'; // doesn't get assigned!
objBefore['should'] = 'should2'; // doesn't get assigned!
console.log('Before:', objBefore);
Object.keys(require.cache).forEach(function(path) {
if (path.indexOf('/node_modules/should/') === -1) return;
console.log('Un-requiring path', path);
delete require.cache[path];
});
var objAfter = {};
objAfter.should = 'should1'; // doesn't get assigned!
objAfter['should'] = 'should2'; // doesn't get assigned!
console.log('After:', objAfter);
// still has shouldJS stuff
console.log('objAfter.should:', objAfter.should);
which gives this:
Before: {}
Un-requiring path /my/path/node_modules/should/index.js
Un-requiring path /my/path/node_modules/should/lib/should.js
...
Un-requiring path /my/path/node_modules/should/lib/ext/contain.js
Un-requiring path /my/path/node_modules/should/lib/ext/promise.js
After: {}
objAfter.should: Assertion { obj: {}, anyOne: false, negate: false, params: { actual: {} } }
None of the .should properties are getting assigned.
There's a way to turn ShouldJS should getter on and off:
require('should');
(1).should.be.equal(1);
// turn off ShouldJS should getter
// and use should as a function
var should = require('should').noConflict();
should(0).be.equal(0);
// turn on ShouldJS should getter
should.extend();
require('should');
(1).should.be.equal(1);
Here's what the official Readme says:
It is also possible to use should.js without getter (it will not even try to extend Object.prototype), just require('should/as-function'). Or if you already use version that auto add getter, you can call .noConflict function.
Results of (something).should getter and should(something) in most situations are the same
I want to stub process.env.FOO with bar.
var sinon = require('sinon');
var stub = sinon.stub(process.env, 'FOO', 'bar');
I'm confused.
I read document, but still I don't understand yet.sinonjs docs
sinonjs is one example, not sinonjs is okay.
From my understanding of process.env, you can simply treat it like any other variable when setting its properties. Keep in mind, though, that every value in process.env must be a string. So, if you need a particular value in your test:
it('does something interesting', () => {
process.env.NODE_ENV = 'test';
// ...
});
To avoid leaking state into other tests, be sure to reset the variable to its original value or delete it altogether:
afterEach(() => {
delete process.env.NODE_ENV;
});
I was able to get process.env to be stubed properly in my unit tests by cloning it and in a teardown method restoring it.
Example using Mocha
const env = Object.assign({}, process.env);
after(() => {
process.env = env;
});
...
it('my test', ()=> {
process.env.NODE_ENV = 'blah'
})
Keep in mind this will only work if the process.env is only being read in the function you are testing. For example if the code that you are testing reads the variable and uses it in a closure it will not work. You probably invalidate the cached require to test that properly.
For example the following won't have the env stubbed:
const nodeEnv = process.env.NODE_ENV;
const fnToTest = () => {
nodeEnv ...
}
With sinon you can stub any variable like this.
const myObj = {
example: 'oldValue',
};
sinon.stub(myObj, 'example').value('newValue');
myObj.example; // 'newValue'
This example is form sinon documentation. https://sinonjs.org/releases/v6.1.5/stubs/
With that knowledge, you can stub any environment variable.
In your case it would look like this:
let stub = sinon.stub(process.env, 'FOO').value('bar');
You can use this if you want to stub a key which not present in process.env
const sinon = require('sinon')
let sandbox = sinon.createSandbox();
sandbox.stub(process, 'env').value({ 'SOME_KEY': 'SOME_VALUE' });
How to quickly mock process.env during unit testing.
https://glebbahmutov.com/blog/mocking-process-env/
const sinon = require('sinon')
let sandbox = sinon.createSandbox()
beforeEach(() => {
sandbox.stub(process.env, 'USER').value('test-user')
})
it('has expected user', () => {
assert(process.env.USER === 'test-user', 'wrong user')
})
afterEach(() => {
sandbox.restore()
})
But what about properties that might not exist in process.env before the test? You can use the following package and then you will be able to test the not exist env variables.
https://github.com/bahmutov/mocked-env
In a spec-helper.coffee or something similar where you set up your sinon sandbox, keep track of the original process.env and restore it after each test, so you don't leak between tests and don't have to remember to reset every time.
_ = require 'lodash'
sinon = require 'sinon'
beforeEach ->
#originalProcessEnv = _.cloneDeep process.env
afterEach ->
process.env = _.cloneDeep #originalProcessEnv
In your test, use process.env as normal.
it 'does something based on an env var', ->
process.env.FOO = 'bar'
Like many others have pointed out the sinon way works unless you have the environment variables set before the test begins, e.g. in the file you are testing like so:
const foo = process.env.YOUR_VAR;
Gleb Bahmutov wrote an npm package that gives a nice way of setting environment variables in your tests no matter how you are referencing the environment variables.
Link to his page: https://glebbahmutov.com/blog/mocking-process-env/
Link to npm package: https://github.com/bahmutov/mocked-env
It worked great and was super easy to implement.
I am playing around and learning about vows with a personal project. This is a small client side library, with testing done in vows. Therefore, I must build and test a file that is written like this:
(function(exports) {
var module = export.module = { "version":"0.0.1" };
//more stuff
})(this);
In my testing (based off of science.js, d3, etc.) requires that module like so:
require("../module");
I continued to get a "module not defined error" when trying to run the tests, so I went to a repl and ran:
require("../module")
and it returned:
{ module: { version: "0.0.1" } }
I realize I could do something like:
var module = require("../module").module;
but feel like I am creating a problem by doing it that way, especially since the libraries that I based this project on are doing it in the format I described.
I would like for my project to behave similar to those which I based it off of, where:
require("../module");
creates a variable in this namespace:
module.version; //is valid.
I have seen this in a variety of libraries, and I am following the format and thought process to the T but believe I might be missing something about require behavior I don't know about.
There is no problem creating it this way. Modules define what they return in the module.exports object. By the way, you don't actually need self executing functions (SEF), there is no global leakage like in browsers :-)
Examples
module1.js:
module.exports = {
module: { 'version': '0.1.1' }
};
main.js:
var module1 = require( './module1.js' );
// module1 has what is exported in module1.js
Once you've understood how this works, you can easily export the version number right away if you want to:
module1.js:
module.exports = '0.1.1';
main.js:
var module1 = require( './module1.js' );
console.log( module1 === '0.1.1' ); // true
Or if you want some logic, you can easily extend your module1.js file like this:
module.exports = ( function() {
// some code
return version;
} () ); // note the self executing part :-)
// since it's self executed, the exported part
// is what's returned in the SEF
Or, as many modules do, if you want to export some utility functions (and keep others "private"), you could do it like this:
module.exports = {
func1: function() {
return someFunc();
},
func2: function() {},
prop: '1.0.0'
};
// This function is local to this file, it's not exported
function someFunc() {
}
So, in main.js:
var module1 = require( './module1.js' );
module1.func1(); // works
module1.func2(); // works
module1.prop; // "1.0.0"
module1.someFunc(); // Reference error, the function doesn't exist
Your special case
About your special case, I wouldn't recommend doing it like they're doing.
If you look here: https://github.com/jasondavies/science.js/blob/master/science.v1.js
You see that they're not using the var keyword. So, they're creating a global variable.
This is why they can access it once they require the module defining the global variable.
And by the way, the exports argument is useless in their case. It's even misleading, since it actually is the global object (equivalent of window in browsers), not the module.exports object (this in functions is the global object, it'd be undefined if strict mode were enabled).
Conclusion
Don't do it like they're doing, it's a bad idea. Global variables are a bad idea, it's better to use node's philosophy, and to store the required module in a variable that you reuse.
If you want to have an object that you can use in client side and test in node.js, here is a way:
yourModule.js:
// Use either node's export or the global object in browsers
var global = module ? module.exports : window.yourModule;
( function( exports ) {
var yourModule = {};
// do some stuff
exports = yourModule;
} ( global ) );
Which you can shorten to this in order to avoid creating the global variable:
( function( exports ) {
var yourModule = {};
// do some stuff
exports = yourModule;
} ( module ? module.exports : window.yourModule ) );
This way, you can use it like this on the client-side:
yourModule.someMethod(); // global object, "namespace"
And on the server side:
var yourModule = require( '../yourModule.js' );
yourModule.someMethod(); // local variable :-)
Just FYI, .. means "parent directory". This is the relative path of where to get the module. If the file were in the same directory, you'd use ..