How to export functions in modules? - javascript

Dear all I just started learning modules in javascript. This is my implementation of a module:
module.exports = function(logger_level) {
this.info = function(message) {
stdout(message);
};
};
function stdout(message)
{
process.stdout.write(message+'\n');
}
and I have to invoke it like this:
var logger = require('./js/logger.js');
var logger1 = new logger("info1");
logger1.info("info");
My question is how to change my implementation of the module so that I could invoke it like this:
var logger = require('./js/logger.js')("info");
logger.info("info");

If you want to use it like that, there's no need for a constructor function at all:
module.exports = {
info: function(message) {
stdout(message);
}
};
function stdout(message)
{
process.stdout.write(message+'\n');
}
I just started learning modules in javascript.
Note that this isn't "modules in JavaScript" so much as it is "modules in NodeJS". JavaScript's modules, which weren't introduced until ES2015 ("ES6"), look and act quite different, but they aren't broadly-supported yet. When they are, your module might look like this:
// ES2015+ syntax
export default {
info: function(message) {
stdout(message);
}
};
function stdout(message)
{
process.stdout.write(message+'\n');
}
...and used like this:
import logger from './logger.js';
logger.info("info");

You're looking for a factory function. Applying it to your use case it would look like:
module.exports = function(logger_level) {
return {
info: function(message) {
stdout(message);
}
};
};
function stdout(message)
{
process.stdout.write(message+'\n');
}

You can wrap your constructor function with an object and return an instance of your constructor function
module.exports = {
return new function(logger_level) {
this.info = function(message) {
stdout(message);
};
};
}
And call it
var logger = require('./js/logger.js');
logger.info("info");
You can also make a singleton pattern to work with a single instance of your constructor function

Related

How to mock call to constructor of class in 3rd party module called from the same module using jest

I have a 3rd party module like this
class Test {
async doSomething() {}
}
export const testObject = new Test(); <--- I want to mock this part because constructor requires some input which I don't want to provide as it is not required for tests
another module which imports the above module
import { testObject } from 'module1';
function foo() {
testObject.doSomething()
}
Now I am trying to write unit tests like below
describe('test', ()=> {
test('', ()=> {
foo()
})
})
new Test() depends on some outside input which I don't want to provide so when i run tests, it fails because of missing input and I am not sure how to stop new Test() from being executed as is and instead a mock functions should be run instead
Try something like this. If you run this test it'll pass:
Class file.
class SoundPlayer {
foo: string
constructor() {
this.foo = 'bar'
}
playSoundFile(fileName: any) {
console.log('Playing sound file ' + fileName)
}
}
export const testObject = new SoundPlayer()
Function file
import { testObject } from './test'
export default function test() {
testObject.playSoundFile('testing')
}
Test file
import { testObject } from '../test'
import test from '../test1'
jest.mock('../test')
it('should run test', () => {
test()
expect(testObject.playSoundFile).toHaveBeenCalledTimes(1)
})
This is a very basic example and there are 4 different ways you can mock.
Check out the Jest documentation: https://jestjs.io/docs/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock

Javascript - How to access class methods within a return function

I've been stuck on this issue for a while. I cannot describe it accurately enough to find solutions online - apologies if it is a duplicate question.
I want to access helloWorld() from module.js:
export function HelperProvider() {
return class Helper {
constructor() {
}
helloWorld() {
console.log('Hello World');
}
}
}
In another file:
import { HelperProvider } from 'module.js'
const helperProvider = HelperProvider;
const helper = new helperProvider();
helper.helloWorld();
However, I encounter the following error:
Uncaught TypeError: helper.helloWorld is not a function
Any help would be very much appreciated.
You need to invoke the function HelperProvider to get the class.
const helperProvider = HelperProvider();
function HelperProvider() {
return class Helper {
constructor() {
}
helloWorld() {
console.log('Hello World');
}
}
}
const helperProvider = HelperProvider();
const helper = new helperProvider();
helper.helloWorld();
You are using module features that's not out of the box in nodejs, if you want to use modules you'll need to set type: "module" in the package.json file... see details
If you wanna use node ways:
module.js
function HelperProvider() {
return class Helper {
constructor() {}
helloWorld() {
console.log("Hello World");
}
};
}
module.exports = HelperProvider;
index.js
const HelperProvider = require("./Helper");
const helperProvider = HelperProvider();
const helper = new helperProvider();
helper.helloWorld();

Convert Namespaced Javascript Functions to Typescript

I know that all javascript is valid typescript but I'd like to start converting my javascript to typescript conventions. I'm battling this one snippet of JS:
My standard Javascript that works
if (MyCompany === undefined) {
var MyCompany = {};
}
MyCompany.Uploader = MyCompany.Uploader || {};
MyCompany.Uploader.Core = function (config) {
'use strict';
function build() {
console.log("building");
}
return {
build: build
};
};
var config = {this: "that};
MyCompany.Uploader.Core(config).build(); // outputs building to console
I've been messing with multiple approaches and I feel like I not close enough.
My failed attempt at converting to Typescript
namespace MyCompany.Uploader {
export var Core = (config:any) => {
function build() {
console.log("building");
}
};
}
let configobj = {here:"there"};
MyCompany.Uploader.Core(configobj).build();
This simply doesn't work. I can't seem to access the build function. I'm sure this is a rookie mistake.
The error I get: Property build does not exist on type void
That's because you did not add an important part of your javascript code into the typescript version, and that's the return object which contains the reference for the build function, it should be:
namespace MyCompany.Uploader {
export var Core = (config: any) {
function build() {
console.log("building");
}
return {
build: build
}
};
}
let configobj = { here: "there" };
MyCompany.Uploader.Core(configobj).build();
You can also define interfaces for the config and the return object:
namespace MyCompany.Uploader {
export interface Config {
here: string;
}
export interface Builder {
build: () => void;
}
export var Core = (config: Config): Builder => {
function build() {
console.log(config.here);
}
return {
build: build
}
};
}
let configobj = { here: "there" };
MyCompany.Uploader.Core(configobj).build();

How can I mock the imports of an ES6 module?

I have the following ES6 modules:
File network.js
export function getDataFromServer() {
return ...
}
File widget.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
I'm looking for a way to test Widget with a mock instance of getDataFromServer. If I used separate <script>s instead of ES6 modules, like in Karma, I could write my test like:
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
However, if I'm testing ES6 modules individually outside of a browser (like with Mocha + Babel), I would write something like:
import { Widget } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(?????) // How to mock?
.andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
Okay, but now getDataFromServer is not available in window (well, there's no window at all), and I don't know a way to inject stuff directly into widget.js's own scope.
So where do I go from here?
Is there a way to access the scope of widget.js, or at least replace its imports with my own code?
If not, how can I make Widget testable?
Stuff I considered:
a. Manual dependency injection.
Remove all imports from widget.js and expect the caller to provide the deps.
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
I'm very uncomfortable with messing up Widget's public interface like this and exposing implementation details. No go.
b. Expose the imports to allow mocking them.
Something like:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
then:
import { Widget, deps } from 'widget.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(deps.getDataFromServer) // !
.andReturn("mockData");
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
This is less invasive, but it requires me to write a lot of boilerplate for each module, and there's still a risk of me using getDataFromServer instead of deps.getDataFromServer all the time. I'm uneasy about it, but that's my best idea so far.
I've started employing the import * as obj style within my tests, which imports all exports from a module as properties of an object which can then be mocked. I find this to be a lot cleaner than using something like rewire or proxyquire or any similar technique. I've done this most often when needing to mock Redux actions, for example. Here's what I might use for your example above:
import * as network from 'network.js';
describe("widget", function() {
it("should do stuff", function() {
let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData")
let widget = new Widget();
expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
expect(otherStuff).toHaveHappened();
});
});
If your function happens to be a default export, then import * as network from './network' would produce {default: getDataFromServer} and you can mock network.default.
Note: the ES spec defines modules as read-only, and many ES transpilers have started honoring this, which may break this style of spying. This is highly dependent on your transpiler as well as your test framework. For example, I think Jest performs some magic to make this work, though Jasmine does not, at least currently. YMMV.
carpeliam is correct, but note that if you want to spy on a function in a module and use another function in that module calling that function, you need to call that function as part of the exports namespace, otherwise the spy won't be used.
Wrong example:
// File mymodule.js
export function myfunc2() {return 2;}
export function myfunc1() {return myfunc2();}
// File tests.js
import * as mymodule
describe('tests', () => {
beforeEach(() => {
spyOn(mymodule, 'myfunc2').and.returnValue = 3;
});
it('calls myfunc2', () => {
let out = mymodule.myfunc1();
// 'out' will still be 2
});
});
Right example:
export function myfunc2() {return 2;}
export function myfunc1() {return exports.myfunc2();}
// File tests.js
import * as mymodule
describe('tests', () => {
beforeEach(() => {
spyOn(mymodule, 'myfunc2').and.returnValue = 3;
});
it('calls myfunc2', () => {
let out = mymodule.myfunc1();
// 'out' will be 3, which is what you expect
});
});
vdloo's answer got me headed in the right direction, but using both CommonJS "exports" and ES6 module "export" keywords together in the same file did not work for me (Webpack v2 or later complains).
Instead, I'm using a default (named variable) export wrapping all of the individual named module exports and then importing the default export in my tests file. I'm using the following export setup with Mocha/Sinon and stubbing works fine without needing rewire, etc.:
// MyModule.js
let MyModule;
export function myfunc2() { return 2; }
export function myfunc1() { return MyModule.myfunc2(); }
export default MyModule = {
myfunc1,
myfunc2
}
// tests.js
import MyModule from './MyModule'
describe('MyModule', () => {
const sandbox = sinon.sandbox.create();
beforeEach(() => {
sandbox.stub(MyModule, 'myfunc2').returns(4);
});
afterEach(() => {
sandbox.restore();
});
it('myfunc1 is a proxy for myfunc2', () => {
expect(MyModule.myfunc1()).to.eql(4);
});
});
I implemented a library that attempts to solve the issue of run time mocking of TypeScript class imports without needing the original class to know about any explicit dependency injection.
The library uses the import * as syntax and then replaces the original exported object with a stub class. It retains type safety so your tests will break at compile time if a method name has been updated without updating the corresponding test.
This library can be found here: ts-mock-imports.
I have found this syntax to be working:
My module:
// File mymod.js
import shortid from 'shortid';
const myfunc = () => shortid();
export default myfunc;
My module's test code:
// File mymod.test.js
import myfunc from './mymod';
import shortid from 'shortid';
jest.mock('shortid');
describe('mocks shortid', () => {
it('works', () => {
shortid.mockImplementation(() => 1);
expect(myfunc()).toEqual(1);
});
});
See the documentation.
I haven't tried it myself, but I think mockery might work. It allows you to substitute the real module with a mock that you have provided. Below is an example to give you an idea of how it works:
mockery.enable();
var networkMock = {
getDataFromServer: function () { /* your mock code */ }
};
mockery.registerMock('network.js', networkMock);
import { Widget } from 'widget.js';
// This widget will have imported the `networkMock` instead of the real 'network.js'
mockery.deregisterMock('network.js');
mockery.disable();
It seems like mockery isn't maintained anymore and I think it only works with Node.js, but nonetheless, it's a neat solution for mocking modules that are otherwise hard to mock.
I recently discovered babel-plugin-mockable-imports which handles this problem neatly, IMHO. If you are already using Babel, it's worth looking into.
See suppose I'd like to mock results returned from isDevMode() function in order to check to see how code would behave under certain circumstances.
The following example is tested against the following setup
"#angular/core": "~9.1.3",
"karma": "~5.1.0",
"karma-jasmine": "~3.3.1",
Here is an example of a simple test case scenario
import * as coreLobrary from '#angular/core';
import { urlBuilder } from '#app/util';
const isDevMode = jasmine.createSpy().and.returnValue(true);
Object.defineProperty(coreLibrary, 'isDevMode', {
value: isDevMode
});
describe('url builder', () => {
it('should build url for prod', () => {
isDevMode.and.returnValue(false);
expect(urlBuilder.build('/api/users').toBe('https://api.acme.enterprise.com/users');
});
it('should build url for dev', () => {
isDevMode.and.returnValue(true);
expect(urlBuilder.build('/api/users').toBe('localhost:3000/api/users');
});
});
Exemplified contents of src/app/util/url-builder.ts
import { isDevMode } from '#angular/core';
import { environment } from '#root/environments';
export function urlBuilder(urlPath: string): string {
const base = isDevMode() ? environment.API_PROD_URI ? environment.API_LOCAL_URI;
return new URL(urlPath, base).toJSON();
}
You can use putout-based library mock-import for this purpose.
Let's suppose you have a code you want to test, let it be cat.js:
import {readFile} from 'fs/promises';
export default function cat() {
const readme = await readFile('./README.md', 'utf8');
return readme;
};
And tap-based test with a name test.js will look this way:
import {test, stub} from 'supertape';
import {createImport} from 'mock-import';
const {mockImport, reImport, stopAll} = createMockImport(import.meta.url);
// check that stub called
test('cat: should call readFile', async (t) => {
const readFile = stub();
mockImport('fs/promises', {
readFile,
});
const cat = await reImport('./cat.js');
await cat();
stopAll();
t.calledWith(readFile, ['./README.md', 'utf8']);
t.end();
});
// mock result of a stub
test('cat: should return readFile result', async (t) => {
const readFile = stub().returns('hello');
mockImport('fs/promises', {
readFile,
});
const cat = await reImport('./cat.js');
const result = await cat();
stopAll();
t.equal(result, 'hello');
t.end();
});
To run test we should add --loader parameter:
node --loader mock-import test.js
Or use NODE_OPTIONS:
NODE_OPTIONS="--loader mock-import" node test.js
On the bottom level mock-import uses transformSource hook, which replaces on the fly all imports with constants declaration to such form:
const {readFile} = global.__mockImportCache.get('fs/promises');
So mockImport adds new entry into Map and stopAll clears all mocks, so tests not overlap.
All this stuff needed because ESM has it's own separate cache and userland code has no direct access to it.
Here is an example to mock an imported function
File network.js
export function exportedFunc(data) {
//..
}
File widget.js
import { exportedFunc } from 'network.js';
export class Widget() {
constructor() {
exportedFunc("data")
}
}
Test file
import { Widget } from 'widget.js';
import { exportedFunc } from 'network'
jest.mock('network', () => ({
exportedFunc: jest.fn(),
}))
describe("widget", function() {
it("should do stuff", function() {
let widget = new Widget();
expect(exportedFunc).toHaveBeenCalled();
});
});
I haven't been able to try it out yet, but (Live demo at codesandbox.io/s/adoring-orla-wqs3zl?file=/index.js)
If you have a browser-based test runner in theory you should be able to include a Service Worker that can intercept the request for the ES6 module you want to mock out and replace it with an alternative implementation (similar or the same as how Mock Service Worker approaches things)
So something like this in your service worker
self.addEventListener('fetch', (event) => {
if (event.request.url.includes("canvas-confetti")) {
event.respondWith(
new Response('const confetti=function() {}; export default confetti;', {
headers: { 'Content-Type': 'text/javascript' }
})
);
}
});
If your source code is pulling in an ES6 module like this
import confetti from 'https://cdn.skypack.dev/canvas-confetti';
confetti();

Exporting Objects with the Exports Object

Say I have one .js file containing a javascript object. I want to be able to access that object and all of its functionality from another .js file in the same directory. Can I simply export this object with the module.exports object and require() it in the other .js file? If this is possible can you give me an example?
If it helps I'm developing with node.
This is the way I create modules:
myModule.js
var MyObject = function() {
// This is private because it is not being return
var _privateFunction = function(param1, param2) {
...
return;
}
var function1 = function(param1, callback) {
...
callback(err, results);
}
var function2 = function(param1, param2, callback) {
...
callback(err, results);
}
return {
function1: function1
,function2: function2
}
}();
module.exports = MyObject;
And to use this module in another JS file, you can simply use require and use your object as normal:
someFile.js
var myObject = require('myModule');
myObject.function1(param1, function(err, result) {
...
});
Of course you can. In my example I use obj to hold my config info. I put it in a file called index.js in config folder. This makes the index the preferred choice to be picked when I import 'config'. I have 2 exports here one for my node and api stuff and the other for my db. You can ignore the first bit where I set the environment.
const environment = {
development: {
isProduction: false
},
production: {
isProduction: true
}
}[ process.env.NODE_ENV || 'development' ];
export default Object.assign({
host: 'localhost',
port: '3000',
remoteApi: {
token: {
'X-Token': '222222222222222222'
},
base: 'https://www.somedomain.com/api'
}
}, environment);
export const db = {
dbHost: 'localhost',
dbPort: 176178
};
Calling import config from '../config'; will pick the default one. And if I specify I can get the db export import { db } from '../config';
In one file:
module.exports.myObj = some object...;
In the other:
Obj = require('myFile.js').myObj;
Everything in a js file on node is local to that file unless you put it in the export object. This actually is very different from JavaScript in a browser--in the browser all files that get imported act together like one big file.
You can kinda think about node files as though you are creating a module object and passing it' into a function surrounding your code.
module = { 'exports' : {} };
(function(module){
//your js file
...
})(module)
the simplest approach, in my opinion:
write Person.js: (note it comes with ctor)
module.exports = function (firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = function () {
return this.firstName + ' ' + this.lastName;
}
}
and consume it:
var person = require('./Person.js');
var person1 = new person('James', 'Bond');
console.log(person1.fullName());
note that in this simple solution all methods are "public".
ref: https://www.tutorialsteacher.com/nodejs/nodejs-module-exports
as per es6 :-
const person = function(name){
console.log(`Name of the person is ${name}`);
}
export default person;
You can export object like
modules.js
export const events = {};
events.click = function (param1, param2...) {
//do something here
}
events.change = function (param1, param2...) {
//do something here
}
events.get = function (key, response) {
//do something here
response({status: "success"})
}
In main.js
import {events} from "./modules"
console.log(events)
Now you can use the Objects
events.get("my_key", (resp) => {
console.log(resp) // {status: "success"}
})

Categories