如何在 Typescript 中使用 Sinon?

Posted

技术标签:

【中文标题】如何在 Typescript 中使用 Sinon?【英文标题】:How do I use Sinon with Typescript? 【发布时间】:2015-03-25 05:11:39 【问题描述】:

如果我将 sinon 与 typescript 一起使用,那么如何将 sinon 模拟转换为我的对象实例?

例如,将返回一个 SinonMock,但我的被测控制器可能需要将特定服务传递给它的构造函数。

var myServiceMock: MyStuff.MyService = <MyStuff.MyService (sinon.mock(MyStuff.MyService));

controllerUnderTest = new MyStuff.MyController(myServiceMock, $log);

sinon 可以和 Typescript 一起使用吗?

【问题讨论】:

【参考方案1】:

您可能需要使用&lt;any&gt; 类型断言来使类型变宽,然后再将其缩小为特定类型:

var myServiceMock: MyStuff.MyService = 
    <MyStuff.MyService> <any> (sinon.mock(MyStuff.MyService));

只是为了澄清 sinon 的一种行为 - 尽管您传递了 MyStuff.MyService,但您传递给 mock 方法的任何内容都仅用于提供更好的错误消息。

如果你想让 mock 有方法和属性,you need to add them。

如果你想自动创建假货,你可以从tsUnit 中获取FakeFactory,它会创建一个带有一些默认值的假货版本,你可以选择覆盖 - 在 javascript 中,这很容易(加上不使用太多的模拟功能,你可以确保你是在测试行为而不是实现)。

FakeFactory的使用示例:

var target = tsUnit.FakeFactory.getFake<RealClass>(RealClass);
var result = target.run();
this.areIdentical(undefined, result);

【讨论】:

是的,我做了类似的事情。我还了解到我错误地使用了 sinon 对象,我没有意识到我需要做的就是在调用中使用原始的 MyService 实例,它使用了 sinon 模拟/存根。无论如何,您在这里提出的内容似乎对我的要求是正确的,谢谢! 我又回到了这个,似乎它不起作用。 myServiceMock 似乎没有任何方法。有什么想法吗? 你给它期待了吗? myServiceMock.expects("doSomething").returns(42); 我试过了,但问题似乎是 sinon 只能模拟和对象的实例,而不是基于构造函数的类型。我想这真的很有意义,因为模拟 javascript 类型会很困难。但是,这让我仍然必须构造这个类的一个实例(将模拟传递给 ctor ),然后模拟该实例。我想不出任何办法,只能手动创建模拟。【参考方案2】:

如果您使用createStubInstance 方法而不是mock,Sinon 可以很容易地基于构造函数创建存根。

使用mocha、chai、sinon 和sinon-chai 的示例可能如下所示:

import * as sinon from 'sinon';
import * as chai from 'chai';

// ... imports for the classes under test

const expect    = chai.expect;
const sinonChai = require("sinon-chai");

chai.use(sinonChai);

describe('MyController', () => 
    it('uses MyService', () => 

        let myService  = sinon.createStubInstance(MyStuff.MyService),
            controller = new MyStuff.MyController(myService as any, ...);

        // ... perform an action on the controller 
        // that calls myService.aMethodWeAreInterestedIn

        // verify if the method you're interested in has been called if you want to
        expect(myService.aMethodWeAreInterestedIn).to.have.been.called;
    );
);

我有 published an article,如果您想了解更多关于不同的测试替身以及如何将它们与 Sinon.js 一起使用的信息,您可能会发现它很有用。

希望这会有所帮助!

一月

【讨论】:

你也可以使用import * as sinonChai from 'sinon-chai';,假设你已经安装了以下类型:npm i --save-dev @types/sinon-chai 这对我不起作用。我收到错误消息“类型‘SinonStubbedInstance’不可分配给类型‘Foo’。类型‘SinonStubbedInstance’中缺少属性‘bar’。”因为createStubInstance 返回一个SinonStubbedInstance&lt;Foo&gt;,但我需要传递给我的测试函数的不是SinonStubbedInstance&lt;Foo&gt;,而是一个实际的Foo 这似乎也不适用于具有带参数的显式构造函数的类型。我得到TypeError: The constructor should be a function.。不幸的是,这篇文章也采用了这种“快乐路径”的方法,就好像具有多参数构造函数的类根本不存在一样! @Frans - 将存根实例传递给消费者时使用 stubbedFoo as unknown as Foo - 参见例如 github.com/serenity-js/serenity-js/blob/master/packages/core/… 谢谢@JanMolak。结果发现我的The constructor should be a function 错误与我假设的原因不同:我正在导入构造函数,但由于某种原因它以undefined 的身份通过,显然undefined 不是一个函数。【参考方案3】:

在 Typescript 中,这可以通过使用 sinon.createStubInstance 和 SinonStubbedInstance 类来实现。

例子:

let documentRepository: SinonStubbedInstance<DocumentRepository>;

documentRepository = sinon.createStubInstance(DocumentRepository);

现在您可以使用该类的所有存根方法进行完整的智能感知了。

示例排列:

documentRepository.delete.resolves(deletedCount: 1);

documentRepository.update.throws(error);

示例断言:

sinon.assert.calledOnce(documentRepository.update);

只有一个地方需要执行类型转换,那就是初始化要进行单元测试的类。

例子:

documentsController =
  new DocumentsController(
    userContext,
    documentRepository as unknown as DocumentRepository);

希望这会有所帮助。 更多关于这个article。

【讨论】:

我收到一个 tslint 错误“找不到名称‘未知’。”。 unknown 是在 TypeScript 3 中引入的。使用更新的 typescript 编译器为我解决了这个问题(在 IntelliJ 中:Settings > Languages & Frameworks > 打字稿.【参考方案4】:

使用ts-sinon。

它让你:

存根对象 存根接口

【讨论】:

这是一个很好的答案!

以上是关于如何在 Typescript 中使用 Sinon?的主要内容,如果未能解决你的问题,请参考以下文章

仅将 TypeScript 环境声明添加到特定文件(如 *.spec.ts)

如何在 Jasmine 单元测试中使用 Sinon 对 jQuery 动画进行假时间?

如何在 Sinon 中模拟链式函数调用

如何使用 sinon 使用 axios.post 模拟异步方法?

如何正确指定使用 ts-sinon 的模拟的两种类型?

如何使用 sinon 存根/模拟从“超类”复制的依赖项方法