ChatGPT解决这个技术问题 Extra ChatGPT

使用 TypeScript 在 Jest 中模拟依赖

当测试在不同文件中具有依赖关系的模块并将该模块分配为 jest.mock 时,TypeScript 会给出一个错误,即方法 mockReturnThisOnce(或任何其他 jest.mock 方法)在依赖项中不存在,这是因为它是以前键入的。

让 TypeScript 从 jest.mock 继承类型的正确方法是什么?

这是一个简单的例子。

依赖

const myDep = (name: string) => name;
export default myDep;

测试.ts

import * as dep from '../depenendency';
jest.mock('../dependency');

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  dep.default.mockReturnValueOnce('return')
}

我觉得这是一个非常常见的用例,不知道如何正确输入。

如果我没记错的话,您必须在导入之前进行模拟。只需切换前 2 行。但我不确定这一点。
@ThomasKleßen 首先评估通过 ES6 import 导入的模块,无论您是否在导入之前放置一些代码。所以这行不通。
@Thomas 对 jest.mock 的调用被提升到代码的顶部 - 我猜是开玩笑的魔法......(ref)但是,这会产生一些陷阱,例如当 calling jest.mock() with the module factory parameter 因此将模拟函数命名为 mock...

A
Artur Górski

您可以使用类型转换,您的 test.ts 应如下所示:

import * as dep from '../dependency';
jest.mock('../dependency');

const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  mockedDependency.mockReturnValueOnce('return');
});

TS 转译器不知道 jest.mock('../dependency'); 更改了 dep 的类型,因此您必须使用类型转换。由于导入的 dep 不是类型定义,因此您必须使用 typeof dep.default 获取其类型。

以下是我在使用 Jest 和 TS 的过程中发现的其他一些有用的模式

当导入的元素是一个类时,您不必使用 typeof 例如:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

const mockedClass = <jest.Mock<SomeClass>>SomeClass;

当您必须模拟一些节点本机模块时,此解决方案也很有用:

import { existsSync } from 'fs';

jest.mock('fs');

const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;

如果您不想使用 jest 自动模拟并更喜欢创建手动模拟

import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';

const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
  // implementation
}));

it('Should throw an error when calling playSomethingCool', () => {
  const testedClass = new TestedClass(testedClassDependencyMock());
});

testedClassDependencyMock() 创建模拟对象实例 TestedClassDependency 可以是类或类型或接口


我不得不使用 jest.fn(() =>... 而不是 jest.fn<TestedClassDependency>(() =>...(我只是在 jest.fn 之后删除了类型转换),因为 IntelliJ 正在抱怨。否则,这个答案对我有帮助,谢谢!在我的 package.json 中使用它:“@types/jest”:“^24.0.3”
哼,它不再适用于最后一个 TS 版本和 jest 24 :(
@Reza 它是自动模拟的,jestjs.io/docs/en/es6-class-mocks#automatic-mock
<jest.Mock<SomeClass>>SomeClass 表达式对我产生了 TS 错误:Conversion of type 'typeof SomeClass' to type 'Mock<SomeClass, any>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'typeof SomeClass' is missing the following properties from type 'Mock<SomeClass, any>': getMockName, mock, mockClear, mockReset, and 11 more.ts(2352)
@the21st 在这种情况下,您应该使用 (SomeClass as unknown) as <jest.Mock<SomeClass>> 之类的东西。请注意,此代码使用了另一种形式的类型转换,现在更受欢迎。
F
François Romain

使用 mocked 帮助程序,如解释 here

// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')

// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)

test('deep', () => {
  // there will be no TS error here, and you'll have completion in modern IDEs
  mockedFoo.a.b.c.hello('me')
  // same here
  expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})

test('direct', () => {
  foo.name()
  // here only foo.name is mocked (or its methods if it's an object)
  expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})

以下是使用 ts-jest 和类的更多示例:github.com/tbinna/ts-jest-mock-examples 和这篇文章:stackoverflow.com/questions/58639737/…
@Tobi 回购中的测试失败
感谢@Kreator 的提醒。看到 same issue as the one reported 了吗?我还无法重现任何问题。
@Kreator 刚刚合并了一个 PR。让我知道问题是否仍然存在
请注意,现在不推荐使用 ts-jest/utils 以支持“官方” jest-mocks 包。
B
Black

针对 TypeScript 版本 3.x 和 4.x 测试了两种解决方案,两者都在转换所需的功能

1) 使用 jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;

2) 使用 jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

这两种解决方案之间没有区别。第二个更短,因此我建议使用那个。

两种投射解决方案都允许在 mockMyFunction 上调用任何笑话模拟函数,例如 mockReturnValuemockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction 可以正常用于 expect

expect(mockMyFunction).toHaveBeenCalledTimes(1);

谢谢!我比公认的答案更喜欢这个,因为 a)它更容易阅读 IMO 和 b)它在 JSX 中工作而不会导致语法错误
我得到“typeError:mockMyFunction.mockReturnValue 不是函数”
@Spock 我已经使用 require 而不是 import 解决了
a
adanilev

我使用来自 @types/jest/index.d.ts 的模式,就在 Mocked 的类型 def 上方(第 515 行):

import { Api } from "../api";
jest.mock("../api");

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");

我很确定你可以做const myApi = new Api() as jest.Mocked<Api>;
@neoflash:在 TypeScript 3.4 中不是严格模式 - 它会抱怨 Api 类型与 jest.Mock<Api> 没有充分重叠。您必须使用 const myApi = new Api() as any as jest.Mock<Api>,我会说上面的那个看起来比双重断言好一点。
@tuptus:严格模式对 3.4 来说是新鲜的吗?你有关于这个的链接吗?
@elmpp:不确定你的意思。 “严格模式”是指在 tsconfig.json 中有 "strict": true。这涵盖了 noImplicitAnystrictNullChecks 等内容,因此您不必为它们单独设置为 true。
如果 API 构造函数参数怎么办?打字稿抱怨我必须通过它们,即使我不需要。
e
exmaxx

扮演 jest.Mock

只需将函数转换为 jest.Mock 就可以了:

(dep.default as jest.Mock).mockReturnValueOnce('return')


C
Chris Kobrzak

用作 jest.Mock,仅此而已

我能想到的在 ts-jest 中模拟导出为 default 的模块的最简洁方法实际上归结为将模块转换为 jest.Mock

代码:

import myDep from '../dependency' // No `* as` here

jest.mock('../dependency')

it('does what I need', () => {
  // Only diff with pure JavaScript is the presence of `as jest.Mock`
  (myDep as jest.Mock).mockReturnValueOnce('return')

  // Call function that calls the mocked module here

  // Notice there's no reference to `.default` below
  expect(myDep).toHaveBeenCalled()
})

好处:

不需要在测试代码中的任何地方引用默认属性 - 您可以引用实际导出的函数名称,

您可以使用相同的技术来模拟命名导出,

没有 * 如在 import 语句中,

无需使用 typeof 关键字进行复杂的转换,

没有像 mocked 这样的额外依赖项。


B
Bruce Lee

这是我对 jest@24.8.0 和 ts-jest@24.0.2 所做的:

资源:

class OAuth {

  static isLogIn() {
    // return true/false;
  }

  static getOAuthService() {
    // ...
  }
}

测试:

import { OAuth } from '../src/to/the/OAuth'

jest.mock('../src/utils/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

describe('createMeeting', () => {
  test('should call conferenceLoginBuild when not login', () => {
    OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
      return false;
    });

    // Other tests
  });
});

这是模拟非默认类及其静态方法的方法:

jest.mock('../src/to/the/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

这应该是从您的类的类型到 jest.MockedClass 或类似的类型的一些类型转换。但它总是以错误告终。所以我只是直接使用它,它工作。

test('Some test', () => {
  OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
    return false;
  });
});

但是,如果它是一个函数,您可以模拟它并进行类型对话。

jest.mock('../src/to/the/Conference', () => ({
  conferenceSuccessDataBuild: jest.fn(),
  conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as 
jest.MockedFunction<
  typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as 
jest.MockedFunction<
  typeof conferenceSuccessDataBuild
>;

M
Milo

我在 @types/jest 中找到了这个:

/**
  * Wrap a function with mock definitions
  *
  * @example
  *
  *  import { myFunction } from "./library";
  *  jest.mock("./library");
  *
  *  const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
  *  expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/

注意:当您执行 const mockMyFunction = myFunction,然后执行类似 mockFunction.mockReturnValue('foo') 的操作时,您也是一个不断变化的 myFunction

来源:https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089


R
Rui Marques

从 Jest 24.9.0 开始,您可以模拟并正确键入 Class/Object/function 和 Jest 属性。

jest.MockedFunction

jest.MockedClass

对于类型化的模拟,我们想要的是模拟对象类型包含模拟对象类型和 Jest 模拟类型的联合。

import foo from 'foo';
jest.mock('foo');

const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;


mockedFoo.mockResolvedValue('mockResult');

// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');

如您所见,您可以手动转换您需要的内容,或者您需要一些东西来遍历所有 foo 的属性/方法来键入/转换所有内容。

为此(深度模拟类型),您可以使用 Jest 中引入的 jest.mocked() 27.4.0

import foo from 'foo';
jest.mock('foo');

const mockedFoo = jest.mocked(foo, true); 

mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed

R
ReFruity

Artur Górski 评价最高的解决方案不适用于最后一个 TS 和 Jest。使用MockedClass

import SoundPlayer from '../sound-player';

jest.mock('../sound-player'); // SoundPlayer is now a mock constructor

const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;

注意:如果您要模拟的是函数,请改用 MockedFunction
k
klugjo

最新的笑话让您可以使用 jest.mocked 轻松地做到这一点

import * as dep from '../dependency';

jest.mock('../dependency');

const mockedDependency = jest.mocked(dep);

it('should do what I need', () => {
  mockedDependency.mockReturnValueOnce('return');
});

Wayyyy wayyyyy 在底部,我们找到了救赎!为我工作 Jest 27.5.1 和 @types/jest 27.4.1
@fullStackChris 如果您在stackoverflow中按最新排序,它不在底部,这是我现在最喜欢的
d
dx_over_dt

这很丑陋,实际上摆脱这种丑陋是我什至查看这个问题的原因,但是要从模块模拟中获得强类型,您可以执行以下操作:

const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;

jest.mock('./dependency');

确保您需要 './dependency' 而不是直接模拟,否则您将获得两个不同的实例化。


J
Jonas Braga

对我来说,这就足够了:

let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
    return {
        add: async (item: queueItemType, ..._args: any) => {
            // then we can use the item that would be pushed to the queue in our tests
            itemQ = item
            return new Promise(resolve => {
                resolve('Mocked')
            })
        },
    }
})

然后,无论何时调用 add 方法,它都会执行上面的代码,而不是将其推送到队列中,在这种情况下。


m
mostruash

最近的一个库用 babel 插件解决了这个问题:https://github.com/userlike/joke

例子:

import { mock, mockSome } from 'userlike/joke';

const dep = mock(import('./dependency'));

// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({ 
  thisIsAMock: jest.fn() 
}));

it('should do what I need', () => {
  dep.mockReturnValueOnce('return');
}

请注意 depmockReturnValueOnce 是完全类型安全的。最重要的是,tsserver 知道 depencency 已导入并分配给 dep,因此 tsserver 支持的所有自动重构也将起作用。

注意:我维护图书馆。