简体   繁体   English

jest spyOn 无法处理索引文件,无法重新定义属性

[英]jest spyOn not working on index file, cannot redefine property

I have UserContext and a hook useUser exported from src/app/context/user-context.tsx .我有从src/app/context/user-context.tsx导出的UserContext和一个钩子useUser Additionally I have an index.tsx file in src/app/context which exports all child modules.此外,我在src/app/context中有一个 index.tsx 文件,它导出所有子模块。

If I spyOn src/app/context/user-context it works but changing the import to src/app/context I get:如果我 spyOn src/app/context/user-context它可以工作,但是将导入更改为src/app/context我得到:

TypeError: Cannot redefine property: useUser at Function.defineProperty (<anonymous>)

Why is that?这是为什么?

Source code:源代码:

// src/app/context/user-context.tsx

export const UserContext = React.createContext({});

export function useUser() {
  return useContext(UserContext);;
}

// src/app/context/index.tsx

export * from "./user-context";
// *.spec.tsx

// This works:
import * as UserContext from "src/app/context/user-context";

// This does not work:
// import * as UserContext from "src/app/context";

it("should render complete navigation when user is logged in", () => {

    jest.spyOn(UserContext, "useUser").mockReturnValue({
        user: mockUser,
        update: (user) => null,
        initialized: true,
    });
})

If you take a look at the js code produced for a re-export it looks like this如果您查看为重新导出而生成的 js 代码,它看起来像这样

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _context = require("context");

Object.keys(_context).forEach(function (key) {
  if (key === "default" || key === "__esModule") return;
  if (key in exports && exports[key] === _context[key]) return;
  Object.defineProperty(exports, key, {
    enumerable: true,
    get: function get() {
      return _context[key];
    }
  });
});

and the error you get is due to the compiler not adding configurable: true in the options for defineProperty , which would allow jest to redefine the export to mock it, from docs并且您得到的错误是由于编译器未在defineProperty的选项中添加configurable: true ,这将允许 jest 重新定义导出以模拟它,来自docs

configurable可配置

true if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.如果可以更改此属性描述符的类型并且可以从相应的 object 中删除该属性,则为 true。 Defaults to false .默认为false

I think you could tweak your config somehow to make the compiler add that, but it all depends on the tooling you're using.我认为您可以以某种方式调整配置以使编译器添加该配置,但这完全取决于您使用的工具。

A more accessible approach would be using jest.mock instead of jest.spyOn to mock the user-context file rather than trying to redefine an unconfigurable export一种更易于访问的方法是使用jest.mock而不是jest.spyOn来模拟用户上下文文件,而不是尝试重新定义不可配置的导出

it("should render complete navigation when user is logged in", () => {
  jest.mock('./user-context', () => {
    return {
      ...jest.requireActual('./user-context'),
      useUser: jest.fn().mockReturnValue({
        user: {},
        update: (user: any) => null,
        initialized: true
      })
    }
  })
});

The UserContext when re-exported from app/context/index.tsx throws that issue since it's a bug with Typescript on how it handled re-exports in versions prior to 3.9.app/context/index.tsx重新导出时的UserContext会引发该问题,因为它是 Typescript 关于如何处理 3.9 之前版本中的重新导出的错误。

This issue was fixed as of version 3.9, so upgrade Typescript in your project to this version or later ones.此问题在 3.9 版本已修复,因此请将项目中的 Typescript 升级到此版本或更高版本。

This issue was reported here and resolved with comments on the fix here此处报告了此问题,并通过此处的修复评论解决了此问题

below is a workaround without version upgrades.以下是没有版本升级的解决方法。

Have an object in your index.tsx file with properties as the imported methods and then export the object.index.tsx文件中有一个 object 属性作为导入的方法,然后导出 object。

inside src/app/context/index.tsxsrc/app/context/index.tsx

import { useUser } from './context/user-context.tsx'

const context = {
  useUser,
  otherFunctionsIfAny
}

export default context;

or this should also work,或者这也应该有效,

import * as useUser from './context/user-context.tsx';

export { useUser };

export default useUser;

Then spy on them,然后监视他们,

import * as UserContext from "src/app/context";

it("should render complete navigation when user is logged in", () => {

    jest.spyOn(UserContext, "useUser").mockReturnValue({
        user: mockUser,
        update: (user) => null,
        initialized: true,
    });
});

Ref 参考

Good to Know :- Besides the issue with re-exports, the previous versions did not support live bindings as well ie, when the exporting module changes, importing modules were not able to see changes happened on the exporter side.很高兴知道:- 除了重新导出的问题外,以前的版本也不支持实时绑定,即,当导出模块更改时,导入模块无法看到导出端发生的更改。

Ex:前任:

Test.js测试.js

let num = 10;

function add() {
    ++num;  // Value is mutated
}

exports.num = num;
exports.add = add;

index.js index.js

在此处输入图像描述

A similar issue but due to the import's path.一个类似的问题,但由于导入的路径。

The reason for this error message (JavaScript) is explained in this post TypeError: Cannot redefine property: Function.defineProperty ()此错误消息(JavaScript)的原因在这篇文章中解释了TypeError: Cannot redefine property: Function.defineProperty ()

Well, people around suggest to use jest.mock() (as in this answer ).好吧,周围的人建议使用jest.mock() (如this answer )。

I wasn't happy with that, because with jest.mock() you should mock functions at the top of your test spec.我对此并不满意,因为使用jest.mock()您应该在测试规范的顶部模拟函数。 I mean, some tests might need different things to be mocked/real.我的意思是,一些测试可能需要不同的东西来模拟/真实。

But then I find out that you can do this.但后来我发现你可以做到这一点。 Put

import * as Foo from 'path/to/file'; 

jest.mock('path/to/file', () => {
  return {
    __esModule: true,    //    <----- this __esModule: true is important
    ...jest.requireActual('path/to/file')
  };
});

...

//just do a normal spyOn() as you did before somewhere in your test:
jest.spyOn(Foo, 'fn');

PS Also could be a one-liner: PS也可以是单行:

jest.mock('path/to/file', () => ({ __esModule: true, ...jest.requireActual('path/to/file') }));

In my case the problem was that i tried to redefine index.tsx located in parential useSteps folder with the same name as hook file:在我的情况下,问题是我试图重新定义index.tsx位于 parential useSteps文件夹中,与钩子文件同名:

my folder structure was like:我的文件夹结构是这样的:

hooks/useSteps/
              index.tsx
              useSteps/useSteps.tsx

It was like this import * as step from './hooks/useSteps';就像这个import * as step from './hooks/useSteps';

but should be like this import * as step from './hooks/useSteps/useSteps';但应该像这样import * as step from './hooks/useSteps/useSteps';

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM