8

I am using Jest to perform unit tests on a Node.js app, where the code source is written in TypeScript and then compiled into JavaScript.

In one of the classes that I wish to test, an external module is imported and a method from this module is used. I want to mock the calls to this method, in order to test only my code.

However, when I run the test, I get the following error:

TypeError: Cannot redefine property: methodName

The problem is that this method has the following Object Properties:

{ value: [Function],
  writable: false,
  enumerable: true,
  configurable: false }

The configurable: false is what makes it a big problem. I cannot redefine the properties before my mock call to make it writable.

Here is what the relevant code looks like:

Tested Class

import externalType from 'external-module-name';

export class ClassName {
    public propertyName: externalType;

    public method(param: string): Promise<any> {
        return new Promise((resolve, reject) => {
            this.propertyName.externalMethod(param)
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
        });
    }
}

Unit Test

import { ClassName } from 'path/to/class';

describe('class', () => {
    const class = new ClassName;

    it("Blahblah", (done) => {
        Object.defineProperty(class['propertyName'], 'externalMethod', {writable: true});
        const spy = jest.spyOn(class['propertyName'], 'externalMethod').mockReturnValue(Promise.resolve());
        class.method('string')
        .then((result) => {
            // Various expect()
            done();
        });
    });
});

What I tried so far

  1. I added the following line in my test:

    Object.defineProperty(class['module'], 'methodName', {writable: true});

  2. I defined my mock call as following:

    jest.spyOn(class['module'], 'methodName').mockReturnValue(Promise.resolve());

  3. I defined my mock call as following:

    class.propertyName.externalMethod = jest.fn().mockImplementation((query) => { return Promise.resolve(); });

  4. I tried to override the property that I'm calling, as following:

    class.propertyName = <any> { externalMethod = (param: any) => { return Promise.resolve(); } }

For this one I get the error TypeError: Cannot assign to read only property externalMethod of object class, which makes sense since readable is set to false.

But everything seems blocked from the attribute configurable. I'm sure there is something that can be done because I am probably not the only one who wants to perform unit tests on a class that imports an external module, as secure as it is.

So my question is: what would be a clean and working way of mocking the external method? And if it is strictly impossible, what would be a way of testing my class without calling that external method?

Thanks in advance!

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Reyedy
  • 918
  • 2
  • 13
  • 36

3 Answers3

8

I found that by doing:

jest.mock('path/to/class');

I was able to get around this.

Guy
  • 65,082
  • 97
  • 254
  • 325
0

This is a bit of a hack, but until I have a cleaner solution, the following has been an acceptable stop-gap:

let override: boolean | undefined;

static before() {
    Object.defineProperty(module, 'readonlyProperty', {
        get() {
            return this.override;
        }
    });
}

@test
public test() {
    this.override = false;
    expect(module.readonlyProperty).to.be.false;
}

---- UPDATE ----

If using sinon sandboxes, use the replace API

const sandbox = sinon.createSandbox();
sandbox.replace(module, 'readonlyProperty', false);
angie
  • 125
  • 1
  • 4
0

This worked for me:

// ========= Mock Object.defineProperty to always allow overriding =========
const originalDefineProperty = Object.defineProperty;
const originalDefineProperties = Object.defineProperties;
Object.defineProperty = (obj, prop, desc) => {
   try {
     return originalDefineProperty(obj, prop, {...desc, configurable: true});
   } catch(e) {
    return originalDefineProperty(obj, prop, desc);
   }
};
Object.defineProperties = (obj, props) => {
  const propsCopy = {...props};
  Object.keys(propsCopy).forEach((key) => {
    propsCopy[key].configurable = true;
  });
  try {
    return originalDefineProperties(obj, propsCopy);
  } catch(e) {
    return originalDefineProperties(obj, props);
  }
};
// =========================================================================

You need to apply it as soon as possible in your tests. If you are using jest, you can put this code snippet inside a js file and load it using "setupFiles" prop in jest config.

niryo
  • 1,275
  • 4
  • 15
  • 26