Definitelytyped: @types/jasmine toHaveBeenCalledWith infers parameters for overloads incorrectly

Created on 18 Feb 2020  路  7Comments  路  Source: DefinitelyTyped/DefinitelyTyped

The function signature for toHaveBeenCalledWith unfortunately breaks if a generic function that consists of overloads is passed. TypeScript is not able to infer the proper overload for functions through generics. Only a single overload signature is considered for parameter inferring. For example:

function hello(idx: number): void;
function hello(item: object): void;
function hello(arg: any): void { /* impl */}

// This won't work because `MatchableArgs` only infers `object` as parameter type.
expect(hello).toHaveBeenCalledWith(1);

This is a known limitation in TypeScript, but it feels inconvenient that workarounds are needed for function overloads. Overloads are not a rare concept seen in TypeScript projects. I'm wondering if it would make sense to go back to the non-inferred parameter types. i.e. simply using any[].

Authors: @zvirja @djungowski @kolodny

Most helpful comment

I've opened a similar bug report (#43989), but after a more thorough search I found this one.

This issue is really annoying and it prevents us from updating to the latest @types/jasmine, we're stuck with v. 3.3.0. It seems to be quite difficult to even apply a reasonable workaround to this issue, even if we wanted to.

I'm not sure how this issue should be addressed properly, but something really needs to be changed.

As a last resort, IMHO it would be OK even to revert back to any[], as @devversion suggested. any[] is at least usable, whereas the current typings aren't.

All 7 comments

Yup I just saw this happen in our project as well, especially for the ts3.1/ typings.
I think the right thing to do here is to unblock users from using those typings, even if it means loosening them.

Here's a repro:

describe("handles overloaded functions in spies", () => {
    class MyClass {
        myFunc(x: number): number;
        myFunc(x: string, y: string): number;
        myFunc(...p: any[]): number {
            return 4;
        }
    }

    it("works for one parameter overload", () => {
        const s = new MyClass();
        spyOn(s, 'myFunc');
        s.myFunc(4);
        expect(s.myFunc).toHaveBeenCalledWith(4);
    });

    it("works for two parameter overload", () => {
        const s = new MyClass();
        spyOn(s, "myFunc");
        s.myFunc("4", "5");
        expect(s.myFunc).toHaveBeenCalledWith("4", "5");
    });
});

This passes in the tests for the outer level jasmine typings, but fails on the ts3.1/ ones.

I'm going to look into making them less restrictive.

I go as far as to say, this is a bug in the typings if it means users are getting type errors when they shouldn't.

I've opened a similar bug report (#43989), but after a more thorough search I found this one.

This issue is really annoying and it prevents us from updating to the latest @types/jasmine, we're stuck with v. 3.3.0. It seems to be quite difficult to even apply a reasonable workaround to this issue, even if we wanted to.

I'm not sure how this issue should be addressed properly, but something really needs to be changed.

As a last resort, IMHO it would be OK even to revert back to any[], as @devversion suggested. any[] is at least usable, whereas the current typings aren't.

I had a PR open against Jasmine and the definition owners decided against making the change.

The current workaround is to use jasmine.NonTypedSpyObj or jasmine.Spy.

See https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jasmine/ts3.1/jasmine-tests.ts#L1762

This is a bummer. Was trying to upgrade to Jasmine 3.5 so we could use the new propertyNames support when using jasmine.createSpyObj. Will likely have to revert back.

At the moment, the code in https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/jasmine/ts3.1/index.d.ts sits as follows and is definitely broken when used against Jasmine 3.5.

interface FunctionMatchers<Fn extends Func> extends Matchers<any> {
    toHaveBeenCalledWith(...params: MatchableArgs<Fn>): boolean;

    /**
     * Add some context for an expect.
     * @param message - Additional context to show when the matcher fails.
     */
    withContext(message: string): FunctionMatchers<Fn>;

    /**
     * Invert the matcher following this expect.
     */
    not: FunctionMatchers<Fn>;
}

This is the only toHaveBeenCalled... method left inside FunctionMatchers<T> at this point, and it really ought to be removed and the declaration from Matchers<T> allowed to stand on its own without being overridden. When running my tests I get the result: error TS2554: Expected 2 arguments, but got 1.

If I comment out the offending declaration from FunctionMatchers<T> in favor of the one from Matchers<T>, everything works as expected.

@mlibby Can you give a minimal repro of this?

Was this page helpful?
0 / 5 - 0 ratings