用打字稿开玩笑地模拟依赖


100

测试在其他文件中具有依赖性的模块时。当将该模块分配为jest.Mock打字稿时,会给出错误mockReturnThisOnce(该方法(或任何其他jest.Mock方法)不依赖),这是因为先前已键入该方法。使打字稿继承jest.Mock中的类型的正确方法是什么?

这是一个简单的例子。

相依性

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

测试

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
如果我没记错的话,在导入之前必须要模拟。只需切换前2行。但是我不确定。
托马斯

3
通过ES6导入的@ThomasKleßen模块import将首先进行评估,无论您是否在导入之前放置了一些代码。所以这行不通。
mgol

@Thomas对jest.mock的调用被提升到代码的顶部-我猜是jest的魔力...(ref)但是,这会产生一些陷阱,例如,在使用模块工厂参数调用jest.mock()时,因此命名了模拟函数如mock...
Tobi

Answers:


105

您可以使用类型转换,并且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 transpiler不知道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;

如果您不想使用开玩笑的自动模拟,而更喜欢创建手动模拟

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可以是类,类型或接口


3
我不得不使用jest.fn(() =>...代替jest.fn<TestedClassDependency>(() =>...(我只是删除了jest.fn之后的类型转换),因为IntelliJ在抱怨。否则,这个答案可以帮助我,谢谢!在我的package.json中使用它:“ @ types / jest”:“ ^ 24.0.3”
A. Masson

做什么jest.mock('./SomeClass');在上面的代码?
Reza

12
嗡嗡声,它在最后的TS版本和开玩笑中不再起作用了:(
Vincent

1
@Reza这是自动模拟,jestjs.io / docs / en / es6
李小龙

8
<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

64

使用mockedts-jest这里解释助手

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

// here the whole foo var is mocked deeply
const mockedFoo = 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(mocked(foo.name).mock.calls).toHaveLength(1)
})

而如果

  • 你用 tslint
  • ts-jest 在您的开发依赖中,

将此规则添加到您的tslint.json"no-implicit-dependencies": [true, "dev"]


这是使用ts-jest和类的更多示例:github.com/tbinna/ts-jest-mock-examples和该帖子:stackoverflow.com/questions/58639737/…–
Tobi

5
这是比投票最高的答案更好的答案。
fakeplasticandroid

@Tobi回购中的测试失败
Kreator

1
@FrançoisRomain-帖子中的链接已
消失

1
@jarodsmk我更新了链接。谢谢
弗朗索瓦·罗曼

18

我使用@ types / jest / index.d.ts中位于def类型def上方的模式(515行):

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

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

2
我很确定您可以做const myApi = new Api() as jest.Mocked<Api>;
snowfrogdev

4
@neoflash:在TypeScript 3.4中不是处于严格模式下-它会抱怨Api类型与重叠不足jest.Mock<Api>。您必须配合使用const myApi = new Api() as any as jest.Mock<Api>,我想说上面的那个看起来比双重声明更好。
paolostyle,

@tuptus:严格模式是否适用于3.4?您是否有关于此的链接?
elmpp '19

@elmpp:不确定您的意思。通过“严格模式”,我的意思是"strict": true在tsconfig.json中。这包括这样的东西noImplicitAnystrictNullChecks等等,所以你不必把它单独设置为true他们。
paolostyle,

我不明白 为什么只存根一个实例的方法,即myApi?它不会将所有其他由类启动的实例存根Api到正在测试的模块中,对吗?
伊万·王

17

对于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)使用笑话

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

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

两种转换解决方案都允许在mockMyFunctionlikemockReturnValuehttps://jestjs.io/docs/en/mock-function-api.html上调用任何jest模拟函数mockResolvedValue

mockMyFunction.mockReturnValue('value');

mockMyFunction 可以正常使用

expect(mockMyFunction).toHaveBeenCalledTimes(1);

谢谢!我比接受的答案更喜欢这个,因为a)更容易阅读IMO,b)它可以在JSX中工作而不会引起语法错误
Dan Fletcher

7

as jest.Mock

只需将函数转换为即可jest.Mock

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


6

这是我对jest@24.8.0ts-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
>;

4

我在@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


4

使用as jest.Mock,别无其他

default我可以想到的模拟ts-jest中导出的模块的最简洁方法实际上归结为将模块转换为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()
})

好处:

  • 不需要default在测试代​​码中的任何地方引用该属性-您可以引用实际的导出函数名称,
  • 您可以使用相同的技术来模拟命名的导出,
  • 没有* as在import语句,
  • 无需使用typeof关键字进行复杂的转换,
  • 没有像这样的额外依赖项mocked

我爱你,终于4小时后,我的搜寻工作告一段落。
LoXatoR

0

最近的库通过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');
}

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

注意:我维护该库。


0

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

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

jest.mock('./dependency');

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.