How to deal with sibling inheritance problem? - javascript

I have a base class that deals with finding/saving items. To reduce the save function complexity (since it uses insert or update depending on whether it's been saved), I thought it best to split it into two classes since a lot of my other methods (not written here) depends on whether or not it's been saved. This is a bare-bones example:
class ItemBase {
constructor (initData) {
this.data = initData
}
find (id) {
const found = Model.find(this.data)
return new SavedItemBase(found)
}
save () {
const saved = this.insert(this.data)
return new SavedItemBase(saved)
}
insert () {}
update () {}
}
class SavedItemBase extends ItemBase {
save () {
this.update()
return this
}
}
Toy:
class Toy extends ItemBase {
constructor () {
this.name = 123
this.id = 456
}
someMethod () {} // This is lost if I return a new SavedItemBase
}
The problem with Toy is that it only extends Item. If I return a new SavedItem in the parent class Item, the original Toy class is lost. Here is a picture of my problem:
How would I get the Toy to return a SavedItemBase instance of itself when it's returned from Base, without rewriting class for Toy (e.g. SavedToyBase)? Or is there a better way to accomplish this?

Related

Mock a top level import of a class with an internal method I also need to mock with jest.fn() for a test- no __mocks__ [duplicate]

Let's suppose I have the following class:
export default class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
sayMyName() {
console.log(this.first + " " + this.last);
}
bla() {
return "bla";
}
}
Suppose I want to create a mocked class where method 'sayMyName' will be mocked and method 'bla' will stay as is.
The test I wrote is:
const Person = require("../Person");
jest.mock('../Person', () => {
return jest.fn().mockImplementation(() => {
return {sayMyName: () => {
return 'Hello'
}};
});
});
let person = new Person();
test('MyTest', () => {
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
})
The first 'expect' statement passes, which means that 'sayMyName' was mocked successfully. But, the second 'expect' fails with the error:
TypeError: person.bla is not a function
I understand that the mocked class erased all methods.
I want to know how to mock a class such that only specific method(s) will be mocked.
Using jest.spyOn() is the proper Jest way of mocking a single method and leaving the rest be. Actually there are two slightly different approaches to this.
1. Modify the method only in a single object
import Person from "./Person";
test('Modify only instance', () => {
let person = new Person('Lorem', 'Ipsum');
let spy = jest.spyOn(person, 'sayMyName').mockImplementation(() => 'Hello');
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
// unnecessary in this case, putting it here just to illustrate how to "unmock" a method
spy.mockRestore();
});
2. Modify the class itself, so that all the instances are affected
import Person from "./Person";
beforeAll(() => {
jest.spyOn(Person.prototype, 'sayMyName').mockImplementation(() => 'Hello');
});
afterAll(() => {
jest.restoreAllMocks();
});
test('Modify class', () => {
let person = new Person('Lorem', 'Ipsum');
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
});
And for the sake of completeness, this is how you'd mock a static method:
jest.spyOn(Person, 'myStaticMethod').mockImplementation(() => 'blah');
Edit 05/03/2021
I see a number of people disagree with the below approach, and that's cool. I do have a slight disagreement with #blade's approach, though, in that it actually doesn't test the class because it's using mockImplementation. If the class changes, the tests will still always pass giving false positives. So here's an example with spyOn.
// person.js
export default class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
sayMyName() {
return this.first + " " + this.last; // Adjusted to return a value
}
bla() {
return "bla";
}
}
and the test:
import Person from './'
describe('Person class', () => {
const person = new Person('Guy', 'Smiley')
// Spying on the actual methods of the Person class
jest.spyOn(person, 'sayMyName')
jest.spyOn(person, 'bla')
it('should return out the first and last name', () => {
expect(person.sayMyName()).toEqual('Guy Smiley') // deterministic
expect(person.sayMyName).toHaveBeenCalledTimes(1)
});
it('should return bla when blah is called', () => {
expect(person.bla()).toEqual('bla')
expect(person.bla).toHaveBeenCalledTimes(1)
})
});
Cheers! 🍻
I don't see how the mocked implementation actually solves anything for you. I think this makes a bit more sense
import Person from "./Person";
describe("Person", () => {
it("should...", () => {
const sayMyName = Person.prototype.sayMyName = jest.fn();
const person = new Person('guy', 'smiley');
const expected = {
first: 'guy',
last: 'smiley'
}
person.sayMyName();
expect(sayMyName).toHaveBeenCalledTimes(1);
expect(person).toEqual(expected);
});
});
Not really answer the question, but I want to show a use case where you want to mock a dependent class to verify another class.
For example: Foo depends on Bar. Internally Foo created an instance of Bar. You want to mock Bar for testing Foo.
Bar class
class Bar {
public runBar(): string {
return 'Real bar';
}
}
export default Bar;
Foo class
import Bar from './Bar';
class Foo {
private bar: Bar;
constructor() {
this.bar = new Bar();
}
public runFoo(): string {
return 'real foo : ' + this.bar.runBar();
}
}
export default Foo;
The test:
import Foo from './Foo';
import Bar from './Bar';
jest.mock('./Bar');
describe('Foo', () => {
it('should return correct foo', () => {
// As Bar is already mocked,
// we just need to cast it to jest.Mock (for TypeScript) and mock whatever you want
(Bar.prototype.runBar as jest.Mock).mockReturnValue('Mocked bar');
const foo = new Foo();
expect(foo.runFoo()).toBe('real foo : Mocked bar');
});
});
Note: this will not work if you use arrow functions to define methods in your class (as they are difference between instances). Converting it to regular instance method would make it work.
See also jest.requireActual(moduleName)
Have been asking similar question and I think figured out a solution. This should work no matter where Person class instance is actually used.
const Person = require("../Person");
jest.mock("../Person", function () {
const { default: mockRealPerson } = jest.requireActual('../Person');
mockRealPerson.prototype.sayMyName = function () {
return "Hello";
}
return mockRealPerson
});
test('MyTest', () => {
const person = new Person();
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
});
rather than mocking the class you could extend it like this:
class MockedPerson extends Person {
sayMyName () {
return 'Hello'
}
}
// and then
let person = new MockedPerson();
If you are using Typescript, you can do the following:
Person.prototype.sayMyName = jest.fn().mockImplementationOnce(async () =>
await 'my name is dev'
);
And in your test, you can do something like this:
const person = new Person();
const res = await person.sayMyName();
expect(res).toEqual('my name is dev');
Hope this helps someone!
I've combined both #sesamechicken and #Billy Reilly answers to create a util function that mock (one or more) specific methods of a class, without definitely impacting the class itself.
/**
* #CrazySynthax class, a tiny bit updated to be able to easily test the mock.
*/
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
sayMyName() {
return this.first + " " + this.last + this.yourGodDamnRight();
}
yourGodDamnRight() {
return ", you're god damn right";
}
}
/**
* Return a new class, with some specific methods mocked.
*
* We have to create a new class in order to avoid altering the prototype of the class itself, which would
* most likely impact other tests.
*
* #param Klass: The class to mock
* #param functionNames: A string or a list of functions names to mock.
* #returns {Class} a new class.
*/
export function mockSpecificMethods(Klass, functionNames) {
if (!Array.isArray(functionNames))
functionNames = [functionNames];
class MockedKlass extends Klass {
}
const functionNamesLenght = functionNames.length;
for (let index = 0; index < functionNamesLenght; ++index) {
let name = functionNames[index];
MockedKlass.prototype[name] = jest.fn();
};
return MockedKlass;
}
/**
* Making sure it works
*/
describe('Specific Mocked function', () => {
it('mocking sayMyName', () => {
const walter = new (mockSpecificMethods(Person, 'yourGodDamnRight'))('walter', 'white');
walter.yourGodDamnRight.mockReturnValue(", that's correct"); // yourGodDamnRight is now a classic jest mock;
expect(walter.sayMyName()).toBe("walter white, that's correct");
expect(walter.yourGodDamnRight.mock.calls.length).toBe(1);
// assert that Person is not impacted.
const saul = new Person('saul', 'goodman');
expect(saul.sayMyName()).toBe("saul goodman, you're god damn right");
});
});
I was trying to get this to work on a class that had already been mocked. Because it had been mocked already, there was no prototype available for me to modify, so I found this workaround.
I don't love this solution, so if anyone knows a better way to update a method to a class that has already been mocked out, I'm all ears.
And just to clarify, the main answers to this question are working with classes that are not mocked out. In my situation, the class has already been mocked out and I'm trying to update one of the methods to the already-mocked class.
My solution:
const previousClassInstance = new PreviouslyMockedClass();
PreviouslyMockedClass.mockImplementation(() => {
return {
// "Import" the previous class methods at the top
...previousClassInstance,
// Then overwrite the ones you wanna update
myUpdatedMethod: jest.fn(() => {
console.log(
"This method is updated, the others are present and unaltered"
);
}),
};
});
I found a way to reproduce the original spyOn behaviour with Typescript and ES6 modules since you get a jest-Error nowadays when you try to use it on a class instance method.
const addTodoSpy = jest.spyOn(storeThatNeedsToBeSpiedOn, 'addTodo');
TypeError: Cannot redefine property: addTodo at Function.defineProperty (<anonymous>)
The advantage of spyOn is that the original method still runs in its original implementation.
In my case the class instance is a mobX store. But I see no reason why it shouldnt work for other class modules.
The way to do it is simply to save a copy of the original method, then creating a mock function with the saved copy as mockImplemtation and the saving this back into the class instance
const storeThatNeedsToBeSpiedOn = new TodoStore();
const keep = storeThatNeedsToBeSpiedOn.addTodo;
const addTodoSpy = jest.fn().mockImplementation(keep);
storeThatNeedsToBeSpiedOn.addTodo = addTodoSpy;
const storeToTest = new SomeOtherStore(storeThatNeedsToBeSpiedOn);
and in the test:
storeToTest.methodThatCallsAddTodoInternally();
expect(addTodoSpy).toBeCalledTimes(1);
the beauty of this is, that the original implementation of the method still runs with all its side effects (if there are any). So you could finish off your test by saying;
expect(storeThatNeedsToBeSpiedOn.todos.length).toEqual(/* one more than before */);
Hope this helps someone out there who is as frustrated as i was ;)

Calling constructor of parent class in static method of child class

I got a problem with calling super inside of the child static method.
const _todos = [];
class Todo {
constructor(title) {
this.title = title;
this.id = Math.round(Math.random() * 100);
_todos.push({title, id: this.id});
console.log(`Todo ID: ${this.id}. DONT FORGET IT!`);
}
static TodoerVersion = '1.8';
static removeTodo(id) {
const todoIndex = _todos.findIndex(t => t.id == id);
_todos.splice(todoIndex, 1);
}
}
class TodoV2 extends Todo {
static addTodo(title) {
super(title);
}
static addDescription(todo, description) {
todo.description = description;
}
static TodoerVersion = '2.0';
};
new TodoV2("play guitar");
Why does it not work?
But if i call super in normal method, it would works just fine.
super is only callable within the constructor function of a class.
Two answers for you:
The question you actually asked.
What I think you should do instead. :-)
Answering the question you asked:
JavaScript is very special in this regard: this has meaning in static methods (as long as you call them correctly, e.g. TodoV2.addTodo("title")): it's the constructor function you called the static method on. And a subclass constructor function inherits from its superclass constructor function (yes, really). So you can access the constructor function for the parent class using Object.getPrototypeOf(this) in the static method:
// This is something unusual that JavaScript actually does support
static addTodo(title) {
const ctor = Object.getPrototypeOf(this);
return new ctor(title);
}
To handle the case where the user may have called addTodo in a way that doesn't set this, you might do something like this to default to TodoV2 if this is undefined:
static addTodo(title) {
const ctor = Object.getPrototypeOf(this ?? TodoV2);
return new ctor(title);
}
What I think you should do instead
You shouldn't be using a static method for this. Instead, define a constructor for TodoV2:
class TodoV2 extends Todo {
constructor(title, description) {
super(title);
this.description = description ?? "";
}
static addTodo(title) { // Why no `description`?
return new this(title); // Or `new TodoV2(title)`
}
}
You might also look into the Symbol.species pattern if you want subclasses to create instances of superclasses.

Good way of accessing "upper class" methods inside a nested class?

Let's say I have a program:
class Program {
constructor() {
this.profileManager = new ProfileManager();
}
saveProgramConfig() {
// ...
}
}
If something happens inside ProfileManager, and it needs to save the program configuration, what would be the best way of accessing this specific instance's saveProgramConfig method from inside the ProfileManager class?
You could pass the instance as an argument so that both instances have a link to each other:
this.profileManager = new ProfileManager(this);
and have ProfileManager save the instance to one of its own properties, and call saveProgramConfig on it when needed.
What the issue with the following approach? you can access the test method of Program inside ProfileManager class.
class Program {
test() {
return "Hello World";
}
}
class ProfileManager extends Program {
constructor() {
super();
}
main() {
return this.test();
}
}
const tp = new B;
console.log(tp.main());

Implementing JS decorator to wrap class

I'm trying to wrap class constructor and inject to some logic by using class decorator. Everything worked fine until I have tried to extend wrapped class: Extended class don't have methods in prototype.
function logClass(Class) {
// save a reference to the original constructor
const _class = Class;
// proxy constructor
const proxy = function(...args) {
const obj = new _class(...args);
// ... add logic here
return obj
}
// copy prototype so intanceof operator still works
proxy.prototype = _class.prototype;
// return proxy constructor (will override original)
return proxy;
}
#logClass
class Base {
prop = 5;
test() {
console.log("test")
}
}
class Extended extends Base {
test2() {
console.log("test2")
}
}
var base = new Base()
base.test()
var ext = new Extended()
console.log(ext.prop)
ext.test()
ext.test2() // TypeError: ext.test2 is not a function
Okay so I tried to figure out what is "wrong" with your code, but I was not able to make it work because it didn't typecheck. So, as a last resort, I'm posting a partial answer of my attempt, which works (with some quirks) so I can help other users who are more savvy with TypeScript.
First of all, the quirks: class decorators in TS cannot modify the structure of a type, so if you wanted to, for example, add a method to the decorated class, you would be able to do it but you would have to eat up/suppress unavoidable type errors (TS2339) when calling those methods.
There is a work around for this in this other question: Typescript adding methods with decorator type does not exist, but you would lose this current clean syntax for decorators if you do this.
Now, my solution, taken more or less directly from the documentation:
function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(args);
// ...add programmatic logic here
// (`super` is the decorated class, of type `T`, here)
}
// ...add properties and methods here
log(message: string) { // EXAMPLE
console.log(`${super.constructor.name} says: ${message}`);
}
}
}
#logClass
class Base {
prop = 5;
test() {
console.log("test");
}
constructor() {}
}
class Extended extends Base {
test2() {
console.log("test2");
}
}
var base = new Base();
base.test();
var ext = new Extended();
console.log(ext.prop);
//base.log("Hello"); // unavoidable type error TS2339
ext.test();
ext.test2();

How to replicate Javascript Constructor & Prototype objects with Class Syntax

I have a fairly simple example of a Container that stashes a value away and allows you to operate on it in isolation.
For my own interest I've translated the basic structure of this object from .prototype to class syntax. But the example uses a funky method for creating new instances of this object and I can't figure out how to replicate it in class syntax (see code below)
const Container = function(x) {
this.val = x
}
Container.prototype.map = function(f) {
return Container.of(f(this.val))
}
Container.of = function(x) { return new Container(x) } // Problem spot
This translates into (class syntax):
class Container {
constructor(x) {
this.val = x
}
map(f) {
return Container.of(f(this.val))
}
of = (x) => { // ????????
return new Container(x)
}
}
I believe that the problem is that the "of" method is simply bound to the single original instance of "Container" as a helper to make it easier to not have to write "new" every time you want to spin up an instance of this class. But I can't figure out how to replicate binding like that with the class syntax.
Is it just impossible to instantiate an own-class from one of the classe's own methods?
just declare the function as static.
class Container {
constructor(x) {
this.val = x
}
map(f) {
return Container.of(f(this.val))
}
static of(x) { // ????????
return new Container(x)
}
}

Categories