Assign an imported function as a class method? - javascript

Is it possible to assign an imported function as a class method, so that it automatically goes on an objects prototype chain?
// Module
module.exports = function testMethod() {
console.log('From test method')
}
// Main
const testMethod = require('./testMethod')
class TestClass {
// Replace this method with imported function
testMethod() {
console.log('From test method')
}
}
const obj = new TestClass()
I was able to attach the method in this constructor using this.testMethod = testMethod but the method did not go on an objects prototype chain.

Assign to the .prototype property of the TestClass so that instances of TestClass will see the imported method:
class TestClass {
}
TestClass.prototype.testMethod = testMethod;
const testMethod = () => console.log('test method');
class TestClass {
}
TestClass.prototype.testMethod = testMethod;
const tc = new TestClass();
tc.testMethod();

Related

How to mock a specific method of a class whilst keeping the implementation of all other methods with jest when the class instance isn't accessible?

Based on this question (How to mock instance methods of a class mocked with jest.mock?), how can a specific method be mocked whilst keeping the implementation of all other methods?
There's a similar question (Jest: How to mock one specific method of a class) but this only applies if the class instance is available outside it's calling class so this wouldn't work if the class instance was inside a constructor like in this question (How to mock a constructor instantiated class instance using jest?).
For example, the Logger class is mocked to have only method1 mocked but then method2 is missing, resulting in an error:
// Logger.ts
export default Logger() {
constructor() {}
method1() {
return 'method1';
}
method2() {
return 'method2';
}
}
// Logger.test.ts
import Logger from './Logger';
jest.mock("./Logger", () => {
return {
default: class mockLogger {
method1() {
return 'mocked';
}
},
__esModule: true,
};
});
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
const logger = new Logger(); // Assume this is called in the constructor of another object.
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2'); // TypeError: logger.method2 is not a function.
});
});
One solution is to extend the Logger class but this results in an undefined error as the Logger is already mocked:
// ...
jest.mock("./Logger", () => {
return {
default: class mockLogger extends Logger {
override method1() {
return 'mocked';
}
},
__esModule: true,
};
});
// ...
expect(logger.method2()).toBe('method2'); // TypeError: Cannot read property 'default' of undefined
Therefore, what could be the correct way to mock only method1 but keep method2's original implementation?
You can use jest.spyOn and provide a mock implementation for method1.
// Logger.test.ts
import Logger from './Logger';
jest.spyOn(Logger.prototype, "method1").mockImplementation(() => "mocked")
describe("Logger", () => {
it("calls method1 & method2 but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
})
})
But in case you have many methods and you want to mock each one of them except one single method, then you can get the original implementation of this one single method using jest.requireActual.
// Logger.test.ts
import Logger from "./Logger";
const mockMethod1 = jest.fn().mockReturnValue("mocked");
const mockMethod3 = jest.fn().mockReturnValue("mocked");
const mockMethod4 = jest.fn().mockReturnValue("mocked");
const mockMethod5 = jest.fn().mockReturnValue("mocked");
jest.mock("./Logger", () =>
jest.fn().mockImplementation(() => ({
method1: mockMethod1,
method2: jest.requireActual("./Logger").default.prototype.method2,
method3: mockMethod3,
method4: mockMethod4,
method5: mockMethod5,
}))
);
describe("Logger", () => {
it("calls all methods but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
expect(l.method3()).toBe("mocked");
expect(l.method4()).toBe("mocked");
expect(l.method5()).toBe("mocked");
});
});
Note: You don't need to define an ES6 class for mocking, a constructor function also just works fine because ES6 classes are actually just syntactic sugar for constructor functions.
Mocking the prototype works:
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
Logger.prototype.method1 = jest.fn(() => 'mocked');
const logger = new Logger();
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2');
});
});
However, I'm not sure if this is the correct way to mock a specific method when the class instance isn't accessible so I'll leave the question open for while in case there are better solutions.

TypeScript generics create instace

I'm trying to create a factory for instantiating my classes with generics. Checked out TypeScript docs and it all works perfectly. In short, this works just fine:
class Person {
firstName = 'John';
lastName = 'Doe';
}
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(Person);
console.log(JSON.stringify(person));
Now define class Person in directory:
export class Person extends BasePerson {
firstName = 'John';
lastName = 'Doe';
}
And when I import Person from other package:
import { Person } from "./directory"
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(Person);
I get error:
Argument of type 'typeof Person' is not assignable to parameter of type 'new () => Person'
How can I get a value of Person instead of typeof Person?
Using TypeScript 3.7.2 and Node v10.13.0.
Could you try this for me please?
import { Person } from "./directory"
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(new Person);
Actual problem here was a parent class of Person -> BasePerson. BasePerson expected an argument in its constructor, so when I tried to call factory.create(Person), Person actually was an typeof because it expected an argument for base class constructor. Problem was solved by deleting the constructor in base class and injecting a property via ioc container, in my case Inversify.

Is a Node.js module a singleton?

I used to implement singleton this way:
class MySomething {
constructor(props) {}
}
let myInstance = null;
module.exports = (props) => {
//first time call
if(props) {
myInstance = new MySomething (props);
return myInstance;
} else {
return myInstance;
}
this assumes that at app.js (entry file) I will call first:
require('./MySomething')(props)
then everywhere in the project I use:
const instanceOfSomething = require('./MySomething')();
I discovered that every time I got a new instance!
What's wrong in my code?
I tried also this way:
class MySomething {...}
const mySomething = (function() {
let myInstance = null;
return {
init: function() {
myInstance = new MySomething();
},
getInstance: function() {
return myInstance ;
}
}
})();
module.exports = mySomething;
and I got the some problem when importing this module from different files, anyone can explain to me?
every require of file create new instance of mySomething
UPDATE
I tried this example now:
class MySomething {...}
const mySomething = {
myInstance: null,
init: function() {
myInstance = new MySomething();
},
getInstance: function() {
return myInstance ;
}
}
};
module.exports = mySomething;
The same problem happened, maybe it is related to my project structure, I will explain it here:
the code below belongs to module "facts"
facts folder contains a folder named "dao" this folder contains MySomething.js (the singleton)
in the facts/index.js I have:
const Localstorage = require('./dao/MySomething');
exports.init = (path) => {
Localstorage.init(path)
}
exports.Localstorage = Localstorage;
Now in a folder named "core" which contains the "facts" folder I re-exported the Localstorage again in "index.js" like this:
const facstModule = require('./facts');
exports.Localstorage = facstModule.Localstorage;
Then in "schedule" folder which contains "Runtime.js" within I write:
const { Localstorage } = require('../core');
setTimeout(() => {
const localstorageIns = Localstorage.getInstance(); //this is always null!
}, 5000)
In app.js file (entry point) I did:
const facts = require('./src/facts');
facts.init(__dirname);
Normally instance will be created before the timeout execute the callaback,
But I noticed that there two instance of Localstorage which is singleton
the cleanest way to do a singleton is
class MyClass () { ... }
module.exports = new MyClass()
if you need a singleton that gets instantiated once, I would do:
class MyClass () { ... }
let myClass
const MyClassSingleton = (...args) => {
if (myClass) {
return myClass
}
myClass = new MyClass(...args)
return myClass
}
module.exports = MyClassSingleton
Every require of file create new instance of mySomething because every time you return new object with init method and getInstance method.
If you need singleton you need do like this:
class MySomething {
constructor() {
if (!MySomething.instance) {
MySomething.instance = this;
}
}
getInstance: function() {
return MySomething.instance;
}
}
module.exports = MySomething;
class Singleton {
constructor() {
this.my_obj;
}
static makeObject() {
if (!this.my_obj) {
this.my_obj = new Singleton();
}
return this.my_obj;
}
add() {
return 1
}
}
// so to get the object we need to call the makeobject method
const obj = Singleton.makeObject()
console.log(obj.add());

Refer to the class object in a class function worked as a callback function in promise

I have a class method which is a callback function in a Promise.
myClass.js:
class myClass {
constructor() {}
doSomethingAsync(resolve, reject) {
let me = this;
console.log(me); // undefined
// .... do something that returns either resolve() or reject()
}
}
export default (new myClass);
app.js
import ClassA from "myClass.js";
new Promise(ClassA.doSomethingAsync).then(() => {
// .... do something if success ....
}
}).catch(() => {
// ... do something if failed ...
})
The problem I have is that me inside doSometingAsync() is supposed to be referring to ClassA but it is shown undefined. How can I refer to ClassA inside that function?
When you extract a reference to a function and pass it to something else, it doesn't maintain any knowledge that it is supposed to be a method of some class. It's just a function reference that some other function will call. As a result, it doesn't maintain the binding to the intance's this. You can maintain the binding to the instance explicitly using bind()
class myClass {
constructor() {
this.name = "mark"
}
doSomethingAsync(resolve, reject) {
let me = this;
console.log(me);
}
}
let ClassA = new myClass
new Promise(ClassA.doSomethingAsync.bind(ClassA)).then(() => {
// .... do something if success ....
}).catch(() => {
// ... do something if failed ...
})
use arrow functions :
class myClass {
constructor() {}
doSomethingAsync = (resolve, reject) => {
let me = this;
console.log(me);
}
}
export default (new myClass);
You need to bind this to the function:
constructor() {
this.doSomethingAsync = this.doSomethingAsync.bind(this);
}
Alternative, you can use an arrow function, but I think you need a specific version of babel for this:
doSomethingAsync = (resolve, reject) => { ... }

Default Object Property

I am playing with ES6 classes and to better manage an array property of the class, I replaced the array with an object and added all the array-related functions (get, add, remove, etc) along with an array sub-property:
class MyClass {
constructor () {
this.date_created = new Date()
}
posts = {
items: [],
get: () => {
return this.posts.items
},
add: (value) => this.posts.items.unshift(value),
remove: (index) => this.posts.items.splice(index, 1)
}
}
So it got me thinking: is there a way to setup that posts object to return the items array by default? i.e. through: MyClass.posts
I thought I could trick it with the get() but didn't work.
If you want to keep the actual array hidden and untouchable except through the methods, it has to be declared in the constructor. Any function that alters it has to be declared in the constructor as well. If that item is to be publicly accessible, it has to be attached to this.
class Post extends Array
{
add(val)
{
this.unshift(val);
}
remove()
{
this.shift();
}
}
class MyClass
{
constructor()
{
this.date_created = new Date()
this.post = new Post();
}
}
let x = new MyClass();
console.log(x.post);
x.post.add(2);
console.log(x.post);
class MyClass {
constructor() {
this.post = [];
}
get post() {
return [1,2,3]
}
set post(val) {
}
}
let someClass = new MyClass();
console.log(someClass.post)
I believe the correct syntax is something as above. your getter and setter is pointing to post, where post is only a variable/key of MyClass, hence your getter and setter should be at MyClass level

Categories

Resources