15

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

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

另一篇文章给出了一个类似但略有不同的解决方案,它给出了相同的错误。

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名称空间,而不是扩展现有的名称空间。

那么我怎样才能创建自己的匹配器,打字稿会满意呢?

4

3 回答 3

12

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');
    })
});
于 2018-03-21T11:29:33.553 回答
4

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

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

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

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

于 2017-03-28T13:51:29.123 回答
1

如果您使用的是 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');
    });

注意:在这些片段中,namespaceandmodule关键字是等效的,但module已被弃用并且namespace是首选(以避免与 ES 模块或 CommonJS 模块混淆)。

于 2020-04-21T08:15:13.087 回答