Javascript 如何存根打字稿接口/类型定义?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37027776/
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
How to stub a Typescript-Interface / Type-definition?
提问by user1879408
I work with Typescript on an AngularJS 1.X project. I use different Javascript libraries for different purposes. To unit-test my source I would like to stub some dependencies using the Typings (= interfaces). I don't want to use the ANY-type and neither to write an empty method for each interface method.
我在 AngularJS 1.X 项目中使用 Typescript。我为不同的目的使用不同的 Javascript 库。为了对我的源代码进行单元测试,我想使用 Typings(= 接口)存根一些依赖项。我不想使用 ANY 类型,也不想为每个接口方法编写一个空方法。
Im looking for a way to do something like that:
我正在寻找一种方法来做这样的事情:
let dependency = stub(IDependency);
stub(dependency.b(), () => {console.log("Hello World")});
dependency.a(); // --> Compile, do nothing, no exception
dependency.b(); // --> Compile, print "Hello World", no exception
The pain I have right now, is that I either use any
and implement all methods which get called in my test case or I implement the interface and implement the full interface. That's too much useless code :(.
我现在的痛苦是,我要么使用any
并实现在我的测试用例中调用的所有方法,要么实现接口并实现完整的接口。这是太多无用的代码:(。
How can I generate an object that has an empty implementation for each method and is typed? I use Sinon for mocking purposes, but im open to use other libraries too.
如何生成每个方法都有空实现并已键入的对象?我将 Sinon 用于模拟目的,但我也愿意使用其他库。
PS: I know that Typescript erases the interfaces...but I still would like to solve that :).
PS:我知道 Typescript 会擦除接口......但我仍然想解决这个问题:)。
采纳答案by J?rgen Tvedt
I think the short answer is that this is not possiblein Typescript, as the language offers no compile-time or run-time "reflection". It's not possible for a mock library to iterate the members of an interface.
我认为简短的回答是,这在 Typescript 中是不可能的,因为该语言不提供编译时或运行时“反射”。模拟库不可能迭代接口的成员。
See thread: https://github.com/Microsoft/TypeScript/issues/1549
请参阅线程:https: //github.com/Microsoft/TypeScript/issues/1549
This is unfortunate for TDD developers, in which mocking a dependency is a central part of the development workflow.
这对 TDD 开发人员来说是不幸的,其中模拟依赖项是开发工作流程的核心部分。
There are a number of techniques for quickly stubbing the methods, however, as described by the other answers. These options might do the job, with a little mental adjustment.
但是,如其他答案所述,有许多技术可以快速对方法进行存根。这些选项可能会完成这项工作,只需稍作心理调整。
Edit: The Typescript Abstract Syntax Tree, AST, is a compile-time "introspection" - which could probably be used to generate mocks. However, I don't know if anyone has made a practical library.
编辑:Typescript 抽象语法树 AST 是一个编译时“内省”——它可能用于生成模拟。不过不知道有没有人做过实用的库。
回答by Daniel J.G.
I have been writing Typescript tests using qUnit and Sinon, and I have experienced exactly the same pain you are describing.
我一直在使用 qUnit 和 Sinon 编写 Typescript 测试,并且我经历了与您所描述的完全相同的痛苦。
Let's assume you have a dependency on an interface like:
假设您依赖于如下接口:
interface IDependency {
a(): void;
b(): boolean;
}
I have managed to avoid the need of additional tools/libraries by using a couple of approaches based on sinon stubs/spies and casting.
通过使用基于 sinon 存根/间谍和铸造的几种方法,我设法避免了对额外工具/库的需求。
Use an empty object literal, then directly assign sinon stubs to the functions used in the code:
//Create empty literal as your IDependency (usually in the common "setup" method of the test file) let anotherDependencyStub = <IDependency>{}; //Set stubs for every method used in your code anotherDependencyStub.a = sandbox.stub(); //If not used, you won't need to define it here anotherDependencyStub.b = sandbox.stub().returns(true); //Specific behavior for the test //Exercise code and verify expectations dependencyStub.a(); ok(anotherDependencyStub.b()); sinon.assert.calledOnce(<SinonStub>anotherDependencyStub.b);
Use object literal with empty implementations of the methods needed by your code, then wrap methods in sinon spies/stubs as required
//Create dummy interface implementation with only the methods used in your code (usually in the common "setup" method of the test file) let dependencyStub = <IDependency>{ a: () => { }, //If not used, you won't need to define it here b: () => { return false; } }; //Set spies/stubs let bStub = sandbox.stub(dependencyStub, "b").returns(true); //Exercise code and verify expectations dependencyStub.a(); ok(dependencyStub.b()); sinon.assert.calledOnce(bStub);
使用空对象字面量,然后直接将 sinon 存根分配给代码中使用的函数:
//Create empty literal as your IDependency (usually in the common "setup" method of the test file) let anotherDependencyStub = <IDependency>{}; //Set stubs for every method used in your code anotherDependencyStub.a = sandbox.stub(); //If not used, you won't need to define it here anotherDependencyStub.b = sandbox.stub().returns(true); //Specific behavior for the test //Exercise code and verify expectations dependencyStub.a(); ok(anotherDependencyStub.b()); sinon.assert.calledOnce(<SinonStub>anotherDependencyStub.b);
使用带有代码所需方法的空实现的对象字面量,然后根据需要将方法包装在 sinon spies/stubs 中
//Create dummy interface implementation with only the methods used in your code (usually in the common "setup" method of the test file) let dependencyStub = <IDependency>{ a: () => { }, //If not used, you won't need to define it here b: () => { return false; } }; //Set spies/stubs let bStub = sandbox.stub(dependencyStub, "b").returns(true); //Exercise code and verify expectations dependencyStub.a(); ok(dependencyStub.b()); sinon.assert.calledOnce(bStub);
They work quite nice when you combine them with sinon sandboxes and common setup/teardown like the one provided by qUnit modules.
当您将它们与 sinon 沙箱和常见的设置/拆卸(如 qUnit 模块提供的设置/拆卸)结合使用时,它们的效果非常好。
- In the common setup you create a new sandbox and the mock object literals for your dependencies.
- In the test you just specify the spies/stubs.
- 在通用设置中,您为您的依赖项创建一个新的沙箱和模拟对象文字。
- 在测试中,您只需指定间谍/存根。
Something like this (using the first option, but would work the same way if you were using the second option):
像这样(使用第一个选项,但如果您使用第二个选项,则工作方式相同):
QUnit["module"]("fooModule", {
setup: () => {
sandbox = sinon.sandbox.create();
dependencyMock = <IDependency>{};
},
teardown: () => {
sandbox.restore();
}
});
test("My foo test", () => {
dependencyMock.b = sandbox.stub().returns(true);
var myCodeUnderTest = new Bar(dependencyMock);
var result = myCodeUnderTest.doSomething();
equal(result, 42, "Bar.doSomething returns 42 when IDependency.b returns true");
});
I would agree this is still not the ideal solution but it works reasonably well, doesn't require extra libraries and keeps the amount of extra code needed to a low manageable level.
我同意这仍然不是理想的解决方案,但它运行得相当好,不需要额外的库并将所需的额外代码量保持在较低的可管理水平。
回答by florinn
Latest TypeMoq(ver 1.0.2) supports mocking TypeScript interfaces, as long as the runtime (nodejs/browser) supports the Proxy global object introduced by ES6.
最新的TypeMoq(ver 1.0.2)支持 mocking TypeScript 接口,只要运行时(nodejs/browser)支持 ES6 引入的 Proxy 全局对象。
So, assuming IDependency
looks like this:
所以,假设IDependency
看起来像这样:
interface IDependency {
a(): number;
b(): string;
}
then mocking it with TypeMoq would be as simple as this:
然后用 TypeMoq 模拟它就像这样简单:
import * as TypeMoq from "typemoq";
...
let mock = TypeMoq.Mock.ofType<IDependency>();
mock.setup(x => x.b()).returns(() => "Hello World");
expect(mock.object.a()).to.eq(undefined);
expect(mock.object.b()).to.eq("Hello World");
回答by PolishDeveloper
There are few libraries that allows to do that TypeMoq
, TeddyMocks
and Typescript-mockify
are probably one of the more popular ones.
很少有库允许这样做TypeMoq
,TeddyMocks
并且Typescript-mockify
可能是比较流行的库之一。
Check the github repositories and pick the one you like better : links:
检查 github 存储库并选择您更喜欢的一个:链接:
- TeddyMocks: https://github.com/mbraude/TeddyMocks
- TypeMoq: https://github.com/florinn/typemoq
- TypeScriptMockify: https://github.com/brechtbilliet/typescript-mockify
- TeddyMocks:https: //github.com/mbraude/TeddyMocks
- TypeMoq:https: //github.com/florinn/typemoq
- TypeScriptMockify:https: //github.com/brechtbilliet/typescript-mockify
You can also use more popular libs like Sinon, but first you have to use an <any>
type and then narrow it to <IDependency>
type (How do I use Sinon with Typescript?)
你也可以使用更流行的库,比如 Sinon,但首先你必须使用一个<any>
类型,然后将其缩小到<IDependency>
类型(我如何将 Sinon 与 Typescript 一起使用?)
回答by dvabuzyarov
You can try moq.ts, but it depends on Proxy object
您可以尝试moq.ts,但这取决于 Proxy 对象
interface IDependency {
a(): number;
b(): string;
}
import {Mock, It, Times} from 'moq.ts';
const mock = new Mock<IDependency>()
.setup(instance => instance.a())
.returns(1);
mock.object().a(); //returns 1
mock.verify(instance => instance.a());//pass
mock.verify(instance => instance.b());//fail
回答by James McMahon
SafeMockis quite nice, but sadly seems like it is unmaintained now. Full disclosure, I used to work with the author.
SafeMock非常好,但遗憾的是它现在似乎没有维护。完全公开,我曾经和作者一起工作。
import SafeMock, {verify} from "safe-mock";
const mock = SafeMock.build<SomeService>();
// specify return values only when mocks are called with certain arguments like this
when(mock.someMethod(123, "some arg")).return("expectedReturn");
// specify thrown exceptions only when mocks are called with certain arguments like this
when(mock.someMethod(123, "some arg")).throw(new Error("BRR! Its cold!"));
// specify that the mock returns rejected promises with a rejected value with reject
when(mock.someMethod(123)).reject(new Error("BRR! Its cold!"));
//use verify.calledWith to check the exact arguments to a mocked method
verify(mock.someMethod).calledWith(123, "someArg");
SafeMock won't let you return the wrong type from mocks.
SafeMock 不会让你从模拟中返回错误的类型。
interface SomeService {
createSomething(): string;
}
const mock: Mock<SomeService> = SafeMock.build<SomeService>();
//Won't compile createSomething returns a string
when(mock.createSomething()).return(123);
回答by pcan
Now it's possible. I released an enhanced version of the typescript compiler that makes interfaces metadata available at runtime. For example, you can write:
现在有可能。我发布了 typescript 编译器的增强版本,它使接口元数据在运行时可用。例如,您可以编写:
interface Something {
}
interface SomethingElse {
id: number;
}
interface MyService {
simpleMethod(): void;
doSomething(p1: number): string;
doSomethingElse<T extends SomethingElse>(p1: Something): T;
}
function printMethods(interf: Interface) {
let fields = interf.members.filter(m => m.type.kind === 'function'); //exclude methods.
for(let field of fields) {
let method = <FunctionType>field.type;
console.log(`Method name: ${method.name}`);
for(let signature of method.signatures) {
//you can go really deeper here, see the api: reflection.d.ts
console.log(`\tSignature parameters: ${signature.parameters.length} - return type kind: ${signature.returns.kind}`);
if(signature.typeParameters) {
for(let typeParam of signature.typeParameters) {
console.log(`\tSignature type param: ${typeParam.name}`); //you can get constraints with typeParam.constraints
}
}
console.log('\t-----')
}
}
}
printMethods(MyService); //now can be used as a literal!!
and this is the output:
这是输出:
$ node main.js
Method name: simpleMethod
Signature parameters: 0 - return type kind: void
-----
Method name: doSomething
Signature parameters: 1 - return type kind: string
-----
Method name: doSomethingElse
Signature parameters: 1 - return type kind: parameter
Signature type param: T
-----
With all these information, you can build stubs programmatically, as you prefer.
有了所有这些信息,您可以根据自己的喜好以编程方式构建存根。
You can find my project here.
你可以在这里找到我的项目。