I have a NodeJS server application which is split to lost of ES6 modules. I am trying to create a kind of "load module handler", a function in the main module, which other modules would require to register a callback, which will be executed after the main module is fully initialized. I am using Babel (with babel-preset-es2015) to transpile ES6 modules into executable JavaScript.
To demonstrate the issue here in short, I've created 2 sample files.
File index.js (application entry, main module):
import * as js2 from "./js2.js";
let toCall = [], // this array handles callbacks from other modules
initialized = false; // flag
export function onInit (cb) { // registers cb to execute after this module is initialized
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
function done () { // initialization is done - execute all registered callbacks
toCall.forEach(f => f());
}
// some important stuff here
// callback(() => {
initialized = true;
done();
// });
And the other module js2.js:
import { onInit } from "./index";
onInit(() => {
console.log("Now I can use initialized application!");
});
All seems to be OK to me, but unfortunately this doesn't work throwing the next error in the first file:
Cannot read property 'push' of undefined
Thing is, there is no toCall variable at this point, but why? Variable toCall is declared before onInit function, it must be ready to use in onInit, mustn't it? How to solve this and is my way rational enough to implement something called "module initialization callbacks"? Are there any other solutions for this?
Thank you for any help and advice.
I found a beautiful implementation for this.
It is needed to separate "onload handler" implementation to individual module. As a result of this example, there will be three files:
index.js:
import * as js2 from "./js2.js";
import { initDone } from "./init.js";
// some important stuff here
// callback(() => {
console.log(`Main module is initialized!`);
initDone();
// });
js2.js:
import { onInit } from "./init.js";
onInit(() => {
console.log("Module js2.js is initialized!");
});
init.js:
let toCall = [], // this array has to handle functions from other modules
initialized = false; // init flag
export function onInit (cb) {
if (initialized) {
cb();
return;
}
toCall.push(cb);
}
export function initDone () {
initialized = true;
toCall.forEach(f => f());
}
And the result:
Main module is initialized!
Module js2.js is initialized!
Related
My mocked utilFunction isn't being used and adding logging to the factory function shows that it's never called. I've already tried searching for jest.mock not working with relative paths and jest.mock not being called for Typescript thinking that it might be related to the mix of JS tests and TS source code or to the different module paths used in the source vs test code.
Code being tested:
// src/foo/fooModule.ts
import { utilFunction } from '../util'
export const foo = () => {
return utilFunction()
}
Test code:
// test/fooModule.test.js
const { foo } = require('../src/foo/fooModule')
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
describe('fooModule tests', () => ...)
The jest.mock call needs to be moved above the imports:
// test/fooModule.test.js
jest.mock('../src/util', () => {
return { utilFunction: () => 'mocked' };
});
const { foo } = require('../src/foo/fooModule')
describe('fooModule tests', () => ...)
My last experience working with Jest prior to this was in a project where the tests were also written in Typescript and babel-jest was used. babel-jest includes babel-jest-hoist which hoists the jest mocks above any imports automatically, so I didn't previously have to worry about the ordering.
Explination
I have a unit testing question using Angular (v 9), Jest (24.9.0), and an ECMAScript Module (dialog-polyfill (v 0.5.1) ). This polyfill adds support for the HTMLDialogElement. The issue is, it has a method registerDialog that needs to be called when the dialog is in the DOM. It takes the dialog element as it's only parameter. This service that I've created, registers the dialog and adds it to an array. Everything works perfectly with the implementation, but the tests fail because it doesn't know what the polyfill is. I keep getting the following error:
TypeError: Cannot read property 'registerDialog' of undefined
Note: I am quite new to testing in Angular and my implementation might not be correct; if so, please let me know.
Second Note: I'm note sure if there is a better way to create a Dialog Element besides calling createElement on the document within the Jest test.
Dialog Service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
import dialogPolyfill from 'dialog-polyfill';
#Injectable({
providedIn: 'root'
})
export class DialogService {
public dialogs$: BehaviorSubject<HTMLDialogElement[]> = new BehaviorSubject<
HTMLDialogElement[]
>([]);
private dialogs: [] = [];
constructor() {}
register(dialog: HTMLDialogElement) {
dialogPolyfill.registerDialog(dialog);
this.dialogs$.next([...this.dialogs, dialog]);
}
}
Dialog Service Spec
import { TestBed } from '#angular/core/testing';
import { DialogService } from './dialog.service';
describe('DialogService', () => {
let service: DialogService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DialogService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should register a dialog', () => {
expect(service.dialogs$.getValue().length).toBe(0);
service.register(document.createElement('dialog'));
expect(service.dialogs$.getValue().length).toBe(1);
});
});
References
Dialog Polyfill
Angular
Jest JS
You need to provide your service in your TestBed:
beforeEach(() => {
TestBed.configureTestingModule({
providers: [DialogService]
});
service = TestBed.inject(DialogService);
});
I am running into a strange issue.
xxx.testFunction(); statement when executed errors out: TypeError: Cannot read property 'testFunction' of undefined, but works when executed with pre-condition if(xxx)..
Issue can be recreated in the following setup
index.js
const { Module1 } = require("./Module1"); //Commenting out this line fixes the issue
const { Module3 } = require("./Module3");
Module3.testFunction();
Module1.js
const { Module2 } = require("./Module2");
class Module1 {
static testFunction() {
console.log("testFunction")
}
}
module.exports = { Module1 };
Module2.js
const { Module3 } = require("./Module3"); //Commenting out this line also fixes the issue
class Module2 {
}
module.exports = { Module2 };
Module3.js
const { Module1 } = require("./Module1");
class Module3 {
static testFunction() {
//if(Module1) //If we merely check this first it works, but directly it doesn't
Module1.testFunction();
}
}
module.exports = { Module3 };
Moreover, if some of the require statements are removed, the function gets executed..
It also gets fixed when order of requires in index.js is changed.
If you're wondering why the extra requires, the reason is that they were needed for other code in the modules.. The above code is a stripped down version of the production code we found the issue in.
As per my understanding variables in nodejs modules are scoped to the module, and should not have any effect on other modules..
Can someone explain this behavior..
It seems that you have a circular dependency. to solve it you can require in the function itself as if you make Module3.js looks like:
class Module3 {
static testFunction() {
const { Module1 } = require("./Module1");
Module1.testFunction();
}
}
module.exports = { Module3 };
I am building firebase function with javascript. Now i have a lot of inter-call function and i plan to move those function into different file to avoid index.js become very messy.
So below is the current file structure:
/functions
|--index.js
|--internalFunctions.js
|--package.json
|--package-lock.json
|--.eslintrc.json
I want to know:
1) How to export the function from internalFunctions.js and import it to index.js.
2) How to call internalFunctions.js function from index.js.
My code is written in JavaScript.
Edited
internalFunction.js will have multiple functions.
First you set the function in your file:
internalFunctions.js:
module.exports = {
HelloWorld: function test(event) {
console.log('hello world!');
}
};
Or if you dont like a lot messing with curly braces:
module.exports.HelloWorld = function(event) {
console.log('hello world!');
}
module.exports.AnotherFunction = function(event) {
console.log('hello from another!');
}
There are also other styles you can use: https://gist.github.com/kimmobrunfeldt/10848413
Then in your index.js file import the file as a module:
const ifunctions = require('./internalFunctions');
And then you can call it directly within your triggers or HTTP handlers:
ifunctions.HelloWorld();
Example:
//Code to load modules
//...
const ifunctions = require('./internalFunctions');
exports.myTrigger = functions.database.ref('/myNode/{id}')
.onWrite((change, context) => {
//Some of your code...
ifunctions.HelloWorld();
//A bit more of code...
});
I have an app with react and redux. My test engine is - Chai
In my reducer (src/my_reducer.js), I try to get token from localStorage like this:
const initialState = {
profile: {},
token: window.localStorage.getItem('id_token') ? window.localStorage.getItem('id_token') : null,
}
In my tests file (test/reducer_spec.js) I have import 'my_reducer' before test cases:
import myReducer from '../src/my_reducer'
And I have an error, if I try to run test - localStorage (or window.localStorage) - undefined.
I need to mock localStorage? If I need, where is the place for it?
I presume you are running your tests with mocha?
mocha tests run in node.js, and node.js does not have a global window variable. But you can easily create one in your tests:
global.window = {};
You can even add the localStorage to it immediately:
global.window = { localStorage: /* your mock localStorage */ }
The mock depends on what you store in your local storage, but for the example code above this might be a reasonable mock object:
var mockLocalStorage = {
getItem: function (key) {
if( key === 'id_token' ){ return /* a token object */; }
return null;
}
}
Of course, for different tests you can have different mocks, e.g. another mock might always return null to test the case that the key cannot be found.
I solve problem with mock-local-storage
My run test command is:
mocha -r mock-local-storage --compilers js:babel-core/register --recursive
For testing purposes I recommend not to make any calls which may have side effects or call external modules in declarations.
Because requiring / importing your reducer implicitly calls window.localStorage.getItem(...) clean testing gets hard.
I'd suggest to wrap your initialization code with a init method so nothing happens if you require/import your module before calling init. Then you can use beforeEach afterEach to cleanly setup mocks/sandboxes.
import myReducer from '../src/my_reducer'
describe('with faked localStorage', function() {
var sandbox
beforeEach(function() {
sandbox = sinon.sandbox.create()
// fake window.localStorage
})
afterEach(function() {
sandbox.restore()
})
describe('the reducer', function() {
before(function() {
myReducer.init()
})
})
})
The second best solution is to postpone the import and use require within the before test hook.
describe('with fake localStorage', function() {
var sandbox
beforeEach(function() {
sandbox = sinon.sandbox.create()
// fake window.localStorage
})
afterEach(function() {
sandbox.restore()
})
describe('the reducer', function() {
var myReducer
before(function() {
myReducer = require('../src/my_reducer')
})
})
})
It is because you are not running Chai in a browser environment.
Try:
// Make sure there is a window object available, and that it has localstorage (old browsers don't)
const initialState = {
profile: {},
// window.localStorage.getItem('id_token') will return null if key not found
token: window && window.localStorage ? window.localStorage.getItem('id_token') : null,
}