在 Typescript 单元测试中模拟

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39792156/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:53:46  来源:igfitidea点击:

Mocking in Typescript unit tests

javascriptunit-testingtypescriptmocking

提问by Andriy Horen

The problem is that mocking in Typescript can get tricky if the object is complex enough (well in any strongly-typed language). You would usually mock some extra stuff just to make code compile and in C# for instance, you can use AutoFixture or similar. On the other hand Javascript is dynamic language and it's possible to mock only part of the object that's needed for test to run.

问题是,如果对象足够复杂(在任何强类型语言中都是如此),那么在 Typescript 中模拟会变得棘手。你通常会模拟一些额外的东西来编译代码,例如在 C# 中,你可以使用 AutoFixture 或类似的东西。另一方面,Javascript 是动态语言,可以只模拟测试运行所需的对象的一部分。

So in Typescript unit test I can declare my dependency using anytype and thus easily mock it. Do you see any drawbacks of such approach?

因此,在 Typescript 单元测试中,我可以使用anytype声明我的依赖项,从而轻松模拟它。您是否看到这种方法的任何缺点?

let userServiceMock: MyApp.Services.UserService = {
    // lots of thing to mock
}

vs

对比

let userServiceMock: any = {
    user: {
         setting: {
             showAvatar: true
         }
    }
}

回答by Terite

My experience with unit tests in TypeScript definitely shows that it's worth to keep all mock object typed. When you leave your mocks with a type of anyit becomes problematic during a rename. IDE won't correctly discover which occurrences of the useror settingsparam should be changed. Of course writing mock object manually with a complete interface is really laborious.

我在 TypeScript 中进行单元测试的经验表明,保留所有模拟对象的类型是值得的。当您将模拟保留为某种类型时,any在重命名期间就会出现问题。IDE 不会正确发现应该更改usersettings参数的哪些出现。当然,手动编写具有完整界面的模拟对象确实很费力。

Fortunately there are two tools for TypeScript that allows creating type-safe mock objects: ts-mockito(inspired by Java mockito) and typemoq(inspired by C# Moq).

幸运的是,TypeScript 有两个工具可以创建类型安全的模拟对象:ts-mockito(受Java mockito启发)和typemoq(受C# Moq启发)。

回答by Mathias Lykkegaard Lorenzen

Now that TypeScript 3 is out, full strong typing can finally be expressed! I took advantage of this and ported NSubstitute to TypeScript.

现在 TypeScript 3 出来了,终于可以表达完整的强类型了!我利用了这一点并将 NSubstitute 移植到了 TypeScript。

It can be found here: https://www.npmjs.com/package/@fluffy-spoon/substitute

可以在这里找到:https: //www.npmjs.com/package/@fluffy-spoon/substitute

I made a comparison versus most popular frameworks here: https://medium.com/@mathiaslykkegaardlorenzen/with-typescript-3-and-substitute-js-you-are-already-missing-out-when-mocking-or-faking-a3b3240c4607

我在这里与最流行的框架进行了比较:https: //medium.com/@mathiaslykkegaardlorenzen/with-typescript-3-and-substitute-js-you-are-already-missing-out-when-mocking-or-faking -a3b3240c4607

Notice how it can create fakes from interfaces, and have full strong typing along the way!

请注意它如何从接口创建伪造品,并在此过程中具有完整的强类型!

回答by Andriy Horen

As pointed out by @Terite anytype on mocks is poor choice as there would be no relationship between mock and its actual type / implementation. So improved solution may be casting partially-mocked object to mocks type:

正如@Teriteany类型在模拟中指出的那样是糟糕的选择,因为模拟与其实际类型/实现之间没有关系。因此改进的解决方案可能是将部分模拟的对象转换为模拟类型:

export interface UserService {
    getUser: (id: number) => User;
    saveUser: (user: User) => void;
    // ... number of other methods / fields
}

.......

......

let userServiceMock: UserService = <UserService> {
    saveUser(user: User) { console.log("save user"); }
}
spyOn(userServiceMock, 'getUser').andReturn(new User());
expect(userServiceMock.getUser).toHaveBeenCalledWith(expectedUserId);

It also worth mention that Typescript won't allow to cast any object that has extra members (superset or derived type). Means that your partial mock is actually of base type to the UserServiceand can be safely cast. e.g.

还值得一提的是,Typescript 不允许强制转换任何具有额外成员(超集或派生类型)的对象。意味着您的部分模拟实际上是 的基本类型UserService并且可以安全地转换。例如

// Error: Neither type '...' nor 'UserService' is assignable to the other.
let userServiceMock: UserService = <UserService> {
     saveUser(user: User) { console.log("save user"); },
     extraFunc: () => { } // not available in UserService
}

回答by dvabuzyarov

As for functional objects you can use mock libraries that support typescript, or javascript libraries that have type definitions. In both cases types exist only in design time. So jasminesjs has Spy functionality and you can use it in type safety way like this:

至于功能对象,您可以使用支持 typescript 的模拟库,或具有类型定义的 javascript 库。在这两种情况下,类型仅存在于设计时。因此 jasminesjs 具有 Spy 功能,您可以像这样以类型安全的方式使用它:

spyOn(SomeTypescriptClass, "SomeTypescriptClassProperty");

The IDE and typescript compiler will handle it properly. The only drawback is that parameters are not supported. If you need type support for parameter you need to use typescript mock libraries. I can add another mock library for typescript moq.ts

IDE 和打字稿编译器将正确处理它。唯一的缺点是不支持参数。如果您需要对参数的类型支持,则需要使用 typescript 模拟库。我可以为typescript moq.ts添加另一个模拟库

As for DTO objects you can use this approach:

至于 DTO 对象,您可以使用这种方法:

export type IDataMock<T> = {
  [P in keyof T]?: IDataMock<T[P]>;
};

export function dataMock<T>(instance: IDataMock<T>): T {
  return instance as any;
}

// so where you need
const obj = dataMock<SomeBigType>({onlyOneProperty: "some value"});

As I remember IDataMock can be replaced with standard Partial interface from typescript.

我记得 IDataMock 可以用打字稿的标准 Partial 接口替换。