Ben Wainwright
Ben Wainwright

Reputation: 4621

object.hasOwnProperty is not a function when spying on es6 module

So I'm doing a little bit of experimental programming to ensure I understand how certain things work, and I'm coming across an error which I don't understand. I can't see any obvious solutions for this problem using Google or on here specifically.

I'm trying to confirm to myself exactly how jest.spyOn() works when applied to module imports. But right now the answer is that it just doesn't.

spy.js:

export const foo = () => {
  console.log("called foo");
};

export const bar = () => {
  console.log("called bar");
};

spy.spec.js:

import * as Foo from "./spy";
import { jest } from "@jest/globals";

test("call foo", () => {
  jest.spyOn(Foo, "bar");
});

package.json:

{
  "type": "module",
  "scripts": {
    "test": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
  },
  "dependencies": {
    "jest": "^27.0.1"
  }
}

When I run this test, I get the following error: TypeError: object.hasOwnProperty is not a function. I'm running node v15.12.0.

Can somebody explain to my

  1. Why this error occurs
  2. What I can do to remove it

Edit

It has been suggested that the problem is to do with the module import lacking a prototype property. This is not the issue - see below

import * as Foo from "./spy";
import { jest } from "@jest/globals";

test("SpyOn plain object", () => {
  const bar = { baz: () => {} };
  expect(bar.prototype).toBeUndefined(); // Pass
  jest.spyOn(bar, "baz"); // No error
});

test("SpyOn import", () => {
  expect(Foo.prototype).toBeUndefined(); // Pass
  jest.spyOn(Foo, "foo"); // Error!
});

Upvotes: 3

Views: 6754

Answers (2)

Mike Tatsuo
Mike Tatsuo

Reputation: 21

I just added the missing method.

Foo.hasOwnProperty = () => Object.hasOwnProperty;
jest.spyOn(Foo, "foo")

Not ideal... but worked. I was using Vue and Jest and solved the error for me.

Upvotes: 2

OfirD
OfirD

Reputation: 10460

I debugged spyOn to see what happens.

Since Foo is an object of type Module, it doesn't have an hasOwnProperty method (see this answer regarding module namespace exotic objects).

Hence, spyOn throws in the following line (the object is the module Foo):

const isMethodOwner = object.hasOwnProperty(methodName); 

If we change it to:

const isMethodOwner = Object.prototype.hasOwnProperty.call(object, methodName);

It would work, but we'll encounter another error(s) in the code that follows, for example here:

object[methodName] = original;

Which throws:

TypeError: Cannot assign to read only property 'bar' of object '[object Module]'

So maybe it's "better" to just reassign object?:

object = Object.assign({}, object);

In which case the following test passes:

test("call bar", () => {
  const spy = jest.spyOn(Foo, "bar");
  spy();
  expect(spy).toHaveBeenCalled();
});

But the following test fails, though I can't tell if it should pass at all?:

test("call bar", () => {
  const spy = jest.spyOn(Foo, "bar");
  Foo.bar();
  expect(spy).toHaveBeenCalled();
});

Let us open a jest issue?

Upvotes: 4

Related Questions