TSR
TSR

Reputation: 20446

How to clone a Javascript class

During unit testing, I want to Mock class Foo with a class called Bar. I have to change a propriety in the class Foo for the test.

This is what I tried. It should print:

Foo

Bar

class Foo {
  id = 'foo'
  constructor() {
    console.log(this.id)
  }
}
const Bar = { ...Foo }
Bar.prototype.id = 'Bar'
new Foo()
new Bar()

I get instead:

error: Uncaught TypeError: Cannot set property 'id' of undefined

I know I can achieve this by creating a new class Bar extends Foo but is there other possible solutions ?

Upvotes: 4

Views: 93

Answers (2)

Aluan Haddad
Aluan Haddad

Reputation: 31833

Object Spread {...o} only considers enumerable properties.

From MDN (emphasis mine):

Spread in object literals The Rest/Spread Properties for ECMAScript proposal (ES2018) added spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.

The prototype property of classes and function is not enumerable. Therefore, in const Bar = {...Foo}, Bar will not have a prototype property. That is precisely why you get

error: Uncaught TypeError: Cannot set property 'id' of undefined

Now, to get the code closer to what you want, you could write

const Bar = {...Foo, prototype: Foo.prototype};

However, now Bar will behave differently from Foo because its prototype property will be enumerable.

So we could revise this and write

const Bar = {...Foo};
Object.defineProperty(Bar, 'prototype', {value: Foo.protoype, enumerable: false});
// `enumerable` defaults to `false` using this API, just being explicit for exposition.

However, your next statement, new B will still fail because B is still not a class object or a function object and therefore cannot be used with the new operator.

Without more context it is difficult to tell why you actually want to do this.

The most straightforward approach would be to write the following

class Foo {
  id = 'Foo';
  constructor() {
    console.log(this.id);
  }
}
class Bar extends Foo {
  id = 'Bar';
}
new Foo() // logs 'Foo'
new Bar() // logs 'Bar'

If you would like to avoid using inheritance, there are other alternatives, but it is not clear which ones are appropriate because the question lacks sufficient context to determine this. For example, if you only want to mock objects created via new Foo, then you could mock them trivially using a helper function that just overwrites the property with the test value, if you want to mock Foo itself, that is to say the class object, then the alternatives would be different.

Upvotes: 2

ichigolas
ichigolas

Reputation: 7725

Instance properties in NodeJS and Babel are not actually set to the prototype. They are set in the constructor.

Check this:

class Foo {
  id = 1
}

new Foo().hasOwnProperty('id') // => true

An object will only lookup a property on its prototype if it's not defined within itself, so overriding the prototype won't work.

If you want to avoid class syntax, you could do something like this:

function Bar() {
  const o = new Foo()
  o.id = 'bar'
  return o
}

new Bar().id // => 'bar'

Upvotes: 1

Related Questions