JavaScript - How to call a method of a imported JS file - javascript

How to define a method 'foo' in a javascript file 'test.js' so that I can import that file into another javascript file and call the method 'foo'?

Before going further you'll need to figure out what kind of modules you're using, there're 2 common types of modules that I know CommonJS and ES
In either type of module you're using, in order for you to import your method, you will need to export it as a module first.
The details of their differences are found in the NodeJS document NodeJS Import
For the ES modules you can export your method 'foo' like this:
// normal function
export function foo1() {}
// arrow function
export const foo2 = () => {}
// or this grouping style that I prefer would look more elegant
function foo1() {}
const foo2 = () => {}
// we can export multiple methods at once
export {
foo1,
foo2
}
On the other hand, using the CommonJS module, you can export your method 'foo' like this:
// a single method
module.exports.foo1 = function() {}
module.exports = function foo2() {}
// multiple methods
module.exports = {
foo1: () => {},
foo2: () => {}
}
Once you've exported your methods as modules, you can now import them like this:
import { foo1, foo2 } from './test.js'
const exec = foo1();
or
import * as test from './test.js'
const exec = test.foo2();
Hope this help

Related

How to get separate "global" module per instance?

Is it possible get individual "global" module for each instance?
import MyModule from './index.js';
const first = new MyModule();
const second = new MyModule();
// PLEASE SEE EXAMPLE BELOW FOR LOGS
// expect to log for both:
// 1. "in entry: true"
// 2. "in init: true"
// but second module logs:
// 1. "in entry: false"
// 2. "in init: false"
Issue being here that both share globals and 1. instance changes property to false.
What are my options to have in individual globals module per instance?
PS! I have tens of thousands of lines of legacy code. Would be 100x better if I didn't need to change/remove/refactor globals.js contents nor these imports import globals from './globals.js';. Nevertheless, give all your ideas - I need to fix it, even if I need to change a lot.
Code for example above / minimal reproducible example
index.js
import globals from './globals.js';
import init from './init.js';
export default function MyModule (conf) {
console.log('in entry:', globals.state1);
init();
}
globals.js
export default {
state1: true,
};
init.js
import globals from './globals.js';
export default function init () {
console.log('in init:', globals.state1);
globals.entry1 = false;
}
Since an object is passed by reference you need to create a copy each time you use globals.
A good way is to export a function like:
/* globals.js */
const globals = { // Maybe wrap the object in Object.freeze() to prevent side effects
state1: true,
};
export default () => ({ ...globals });
You can then call the function to create a copy each time you need it.
/* init.js */
import getGlobals from './globals.js';
export default function init () {
const globals = getGlobals();
console.log('in init:', globals.state1);
globals.entry1 = false;
}

Jasmine mockability of imported vs required named exports

I'll start with the setup, before I get into the problem. Let's start with the base file whose named exports we'll want to spy on:
// fileA.js
export function bar() {}
export function foo() {}
And then have two variations of classes that import these, one in CommonJS style, and one in ES6 style:
// es6Baz.js
import * as A from 'fileA'
export default class Baz {
callFoo() { A.foo(); }
}
// commonBaz.js
const A = require('fileA');
export default class Baz {
callFoo() { A.foo(); }
}
Then likewise, test files for these two same variants:
// testEs6A.js
import Baz from 'es6Baz';
import * as A from 'fileA';
it('test', () => {
spyOn(A, 'foo');
const b = new Baz();
b.callFoo();
expect(A.foo).toHaveBeenCalled(); // this will fail
});
// testCommonA.js
import Baz from 'es6Baz';
const A = require('fileA');
it('test', () => {
spyOn(A, 'foo');
const b = new Baz();
b.callFoo();
expect(A.foo).toHaveBeenCalled(); // this will pass
});
The main question here is: why does mocking work with the CommonJS approach, but not the ES6 one, assuming we're using Babel to compile?
My understanding was that ES6 live binds, while CommonJS copies, so I was surprised the former failed, and even more surprised the latter succeeded. My best guess is that a * import results in a locally namespaced object that's different in the files this is done in (i.e. A in es6Baz.js is not the same as A in testEst6A.js), but is that actually the case? And why would the require work, when this doesn't?

How to create a definition for 'module.exports' function

I am exporting a function like this which works.
module.exports = function (options: any): RequestHandler {
// Do stuff
}
I am trying to add a definition for the exported function, but I am not sure if this is the right way to do it:
declare global {
export function tsm(options: any): RequestHandler
}
When I try to test it both of the following say that this is valid:
const tsm = require('ts-middleware')
global.tsm() // Gives intellisense
tsm() // Also gives intellisense
It shouldn't give information about global.tsm(), so I think that I created my definition wrong. What is the correct way to create a function definition?
I don't want to use the function like this:
const tsm = require('ts-middleware')
tsm.tsm()
But I do want to use it like this:
const tsm = require('ts-middleware')
tsm()
To define typings for a module, use declare module 'x' {...}. i.e.:
declare module 'ts-middleware' {
export = (option: any): RequestHandler
}
However, you do not really need to do that because you wrote your code in TypeScript. The compiler can generate the typings for you automatically.
You just need to add declaration: true in your tsconfig.json:
// tsconfig.json
{
"compilerOptions": {
"declaration": true
}
}
Also, I would strongly recommend you to write your code using ESM. i.e. instead of module.exports = ...:
// named export, preferred
export function foo(options: any): RequestHandler { ... }
// or default export
export default function foo(options: any): RequestHandler { ... }
// import in TypeScript for named export
import { foo } from 'ts-middleware'
// import in TypeScript for default export
import foo from 'ts-middleware'
// require in JavaScript commonjs
const middleware = require('ts-middleware')
middleware.foo(...)

ES6 access exported 'default' instance from same file

I was wondering, when you export some data using the ES6 keyword export like so :
export default {
data: "Hello !"
}
How can you then from the same file, access that exact same exported data ?
EDIT: Of course, without declaring it before exporting the variable...
If you structure your file like that, you cannot.
Usually, you define functions and data that you want to expose before the export statement, and then reference them when you build the exported object/function.
Bad way:
export default {
data: 'Hello!',
myFn: function (str) {
console.log(str);
}
}
Good way:
var data = 'Hello!';
var myFn = function (str) {
console.log(str);
};
// code that uses data and myFn
// shorthand for { data: data, myFn: myFn }
export default { data, myFn };
Try the following
export const data = 'hello';
For my similar use case -- I wanted easy access to everything the ES6 module exported from within the same module just for debugging purposes. So:
export const data = 'hi'
export const someFunc = () => `important data: ${data}`
export default someFunc()
export const funcToDebug() => {
console.log(`what's going on here? ${myModule.exports.default}`)
}
var myModule = module
If you want to access internally the functions that you export, you should define them outside of the export statement. Like so:
const data = 'Hello !';
const someFunc = () => {
return data + ' Goodbye!'
}
export default {
data,
someFunc
}
Now the variables and functions can reference each other without being "stuck" inside the export function. For example the following will NOT work:
// WRONG!
export default {
data: 'Hello !',
someFunc: () => {
return data + ' Goodbye!';
}
}

How to stub exported function in ES6?

I have file foo.js:
export function bar (m) {
console.log(m);
}
And another file that uses foo.js, cap.js:
import { bar } from 'foo';
export default m => {
// Some logic that I need to test
bar(m);
}
I have test.js:
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
Somehow I need override implementation of bar(m) in test. Is there any way to do this?
P.S. I use babel, webpack and mocha.
Ouch.. I found solution, so I use sinon to stub and import * as foo from 'foo' to get object with all exported functions so I can stub them.
import sinon from 'sinon';
import cap from 'cap';
import * as foo from 'foo';
sinon.stub(foo, 'bar', m => {
console.log('confirm', m);
});
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
You can replace/rewrite/stub exports only from within the module itself. (Here's an explanation)
If you rewrite 'foo.js' like this:
var bar = function bar (m) {
console.log(m);
};
export {bar}
export function stub($stub) {
bar = $stub;
}
You can then override it in your test like this:
import cap from 'cap'
import {stub} from 'foo'
describe('cap', () => {
it('should bar', () => {
stub(() => console.log('stubbed'));
cap('some'); // will output 'stubbed' in the console instead of 'some'
});
});
I've created a Babel plugin that transforms all the exports automatically so that they can be stubbed: https://github.com/asapach/babel-plugin-rewire-exports
While #Mike solution would work in old versions of sinon, it has been removed since sinon 3.0.0.
Now instead of:
sinon.stub(obj, "meth", fn);
you should do:
stub(obj, 'meth').callsFake(fn)
Example of mocking google oauth api:
import google from 'googleapis';
const oauth2Stub = sinon.stub();
sinon.stub(google, 'oauth2').callsFake(oauth2Stub);
oauth2Stub.withArgs('v2').returns({
tokeninfo: (accessToken, params, callback) => {
callback(null, { email: 'poo#bar.com' }); // callback with expected result
}
});
You can use babel-plugin-rewire (npm install --save-dev babel-plugin-rewire)
And then in test.js use the __Rewire__ function on the imported module to replace the function in that module:
// test.js
import sinon from 'sinon'
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
const barStub = sinon.stub().returns(42);
cap.__Rewire__('bar', barStub); // <-- Magic happens here
cap('some');
expect(barStub.calledOnce).to.be.true;
});
});
Be sure to add rewire to your babel plugins in .babelrc:
// .babelrc
{
"presets": [
"es2015"
],
"plugins": [],
"env": {
"test": {
"plugins": [
"rewire"
]
}
}
}
Lastly, as you can see the babel-plugin-rewire plugin is only enabled in the test environment, so you should call you test runner with the BABEL_ENV environment variable set to test (which you're probably doing already):
env BABEL_ENV=test mocha --compilers js:babel-core/register test-example.js
Note: I couldn't get babel-plugin-rewire-exports to work.
This was definitely a gotcha for me too...
I created a little util to workaround this limitation of sinon. (Available in js too).
// mockable.ts 👇
import sinon from 'sinon'
export function mockable<T extends unknown[], Ret>(fn: (...fnArgs: T) => Ret) {
let mock: sinon.SinonStub<T, Ret> | undefined
const wrapper = (...args: T) => {
if (mock) return mock(...args)
return fn(...args)
}
const restore = () => {
mock = undefined
}
wrapper.mock = (customMock?: sinon.SinonStub<T, Ret>) => {
mock = customMock || sinon.stub()
return Object.assign(mock, { restore })
}
wrapper.restore = restore
return wrapper
}
If you paste the above snippet into your project you can use it like so
foo.js
import { mockable } from './mockable'
// we now need to wrap the function we wish to mock
export const foo = mockable((x) => {
console.log(x)
})
main.js
import { foo } from './foo'
export const main = () => {
foo('asdf') // use as normal
}
test.js
import { foo } from './foo'
import { main } from './main'
// mock the function - optionally pass in your own mock
const mock = foo.mock()
// test the function
main()
console.assert(mock.calledOnceWith('asdf'), 'not called')
// restore the function
stub.restore()
The benefit of this approach is that you don't have to remember to always import the function in a certain way. import { foo } from './foo' works just as well as import * as foo from './foo'. Automatic imports will likely just work in your IDE.

Categories

Resources