【问题标题】:Create custom jasmine matcher using Typescript使用 Typescript 创建自定义茉莉花匹配器
【发布时间】:2017-08-14 20:03:22
【问题描述】:

我在一个 angular2 项目中使用 jasmine,并且在为测试编写自定义匹配器时遇到了一些麻烦。我希望能够比较两个相对复杂的对象。我发现 this article 声称可以解决问题,但它只会导致打字稿错误,指出它无法识别 jasmine 的 Matchers 对象上的新方法。相关代码是这样的:

declare module jasmine {
    interface Matchers {
        toBeNumeric(): void;
    }
}

Another article 给出了一个类似但略有不同的解决方案,但给出了相同的错误。

declare namespace jasmine {
    interface Matchers {
        toHaveText(expected: string): boolean;
    }
}

我试过了

let m: jasmine.Matchers = expect(someSpy.someMethod).toHaveBeenCalled();

得到了这个错误:

类型“jasmine.Matchers”不可分配给类型“jasmine.Matchers”。 存在同名的两种不同类型,但它们不相关。

这似乎表明declare namespace jasmine 语句正在创建一个新的jasmine 命名空间,而不是扩展现有的命名空间。

那么,我怎样才能创建自己的匹配器,让 typescript 满意呢?

【问题讨论】:

标签: typescript jasmine jasmine-matchers


【解决方案1】:

Daf 的回答主要对我有用,我只是注意到他的示例代码和他命名文件的方式存在问题。我还遇到了另一个不相关的问题。因此有了新的答案。

  • 由于某种原因,当接口文件与匹配器文件同名时,我的应用程序不喜欢它。例如 foo.ts 和 foo.d.ts。对于我的应用,它需要是 foo.ts 和 foo-interface.d.ts 或类似的东西。
  • 也不要将接口从 foo.ts 导入到 foo-interface.d.ts 它似乎也不喜欢这样。

匹配器 - custom-matchers.ts

import MatchersUtil = jasmine.MatchersUtil;
import CustomMatcherFactories = jasmine.CustomMatcherFactories;
import CustomEqualityTester = jasmine.CustomEqualityTester;
import CustomMatcher = jasmine.CustomMatcher;
import CustomMatcherResult = jasmine.CustomMatcherResult;

export const SomeCustomMatchers: CustomMatcherFactories = {
    toReallyEqual: function (util: MatchersUtil, customEqualityTester: CustomEqualityTester[]): CustomMatcher {
        return {
            compare: function (actual: any, expected: any, anotherCustomArg: any): CustomMatcherResult {

                // Your checks here.
                const passes = actual === expected;

                // Result and message generation.
                return {
                    pass: passes,
                    message: passes ? `Actual equals expected`
                                    : `Actual does not equal expected`,
                }
            }
        }
    }
};

注意 compare 函数可以有任意数量的自定义参数(甚至是可变参数), 并且只需要/保留第一个参数(以了解实际值); 但是如果函数名以“toHave”开头(而不是toReallyEqual),那么第二个参数保留给“key: string”(要知道对象的字段名,我的意思是,Jasmine2 会为我们循环)。

此外,我们可以中继 Jasmine 来生成消息,例如:

message: util.buildFailureMessage('toReallyEqual', passes, actual, expected, anotherCustomArg),

接口文件 - matcher-types.d.ts - 不能与您的匹配器文件同名

declare namespace jasmine {
    interface Matchers<T> {
        toReallyEqual(expected: any, anotherCustomArg: any, expectationFailOutput?: any): boolean;
    }
}

自定义匹配器测试

describe('Hello', () => {

    beforeEach(() => {
        jasmine.addMatchers(SomeCustomMatchers)
    });

    it('should allow custom matchers', () => {
        expect('foo').toReallyEqual('foo');
        expect('bar').not.toReallyEqual('test');
    })
});

【讨论】:

  • 两个小观察:在toReallyEqual 函数的定义中,您使用参数customEqualityTester,它是jasmine.CustomEqualityTester 的数组,因此考虑到这是一个数组,更好的名称可能是@ 987654333@(复数)。
  • 第二个观察结果是,当您在 if 句子中包含 return 时,您不需要包含 else
  • +1 这种方法有效(完美)。我在其他地方看到了其他建议,建议您可以在规范文件中使用“声明命名空间”,但无论出于何种原因,当您这样做时,打字稿不会合并接口。
  • 不只是“你的应用程序”不喜欢文件命名相同,只是扩展名不同。请参阅此以获得简单的解释:github.com/Microsoft/TypeScript/issues/7624.
【解决方案2】:

如果您使用的是 ES 模块,则命名空间声明需要包装在 declare global 块中。

这是两个自定义匹配器合并到同一定义中的更新示例:

    declare global {
        namespace jasmine {
            interface Matchers {
                toBeNumeric(): void;
                toHaveText(expected: string): boolean;
            }
        }
    }

也可以将声明分开(甚至分布在多个文件中):

    // File 1 declares this matcher
    declare global {
        namespace jasmine {
            interface Matchers {
                toBeNumeric(): void;
            }
        }
    }

    // File 2 declares this matcher
    declare global {
        namespace jasmine {
            interface Matchers {
                toHaveText(expected: string): boolean;
            }
        }
    }

    // File 3: use the custom matchers
    it(function(){
        expect(3).toBeNumeric();
        expect(result).toHaveText('custom matcher');
    });

注意:在这些 sn-ps 中,namespacemodule 关键字是等效的,但不推荐使用 module 而首选 namespace(以避免与 ES 模块或 CommonJS 模块混淆)。

【讨论】:

    【解决方案3】:

    基本上,您的第二个示例(“声明命名空间”)是要走的路,当然,您的匹配器逻辑在其他地方。

    欢迎您查看https://github.com/fluffynuts/polymer-ts-scratch/tree/5eb799f7c8d144dd8239ab2d2bcc72821327cb24/src/specs/test-utils/jasmine-matchers,我在其中编写了一些 Jasmine 匹配器和类型以配合它们 - 尽管从技术上讲,我用 Javascript 编写了实际的匹配器,并且只是将逻辑文件命名为 .ts 来安抚我的构建过程。

    需要安装@types/jasmine -- 并保持最新状态。

    请记住,@types/jasmine 的不同版本可能会破坏事情;具体来说,上面链接的提交是当 Jasmine 类型引入具有类型参数(即Matchers&lt;T&gt;)的 Matchers 类型时,它破坏了我所有的 .d.ts 文件。

    【讨论】:

    • 谢谢@daf - 我从你的例子中模仿了我的例子,它在运行时工作,但打字稿仍然抱怨TS2339: Property 'toEqualWithoutSymbol' does not exist on type 'Matchers&lt;void&gt;'.不确定我错过了什么,但文件肯定包含在内(因为打字稿运行它)。
    • 我发现一堆额外的三斜杠指令给了我错误的错误。我清理了旧项目,这些错误就消失了。所以这个示例项目效果很好!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-21
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多