Skip to content

Proposal: partialMocked - mocked, but less strict return/resolved values. #1065

@dylang

Description

@dylang

Issue :

I want to do something like this...

const mockFoo = mocked(foo);
mockFoo.mockReturnValue({
  valueNeededForTest: 'string' // TypeScript mad! foo returns other values too!
});

But because foo returns a dozen other properties that the test doesn't rely on, so we get errors like this:

Error:(21, 43) TS2345: Argument of type '{ valueNeededForTest: string }' is not assignable to parameter of type '{ buildPath: string; babelReactIntlPath:
string; cachePath: string; coveragePath: string; isCI: boolean; githubHostname: string; githubOrg: string; githubRepo: string; localPublicPath: string; ... 7 more ...; getPathWithoutRepoDirectory: 
(pathToShorten: string) => string; }'.
Type '{ valueNeededForTest: string; }' is missing the following properties from type '{ buildPath: string; babelReactIntlPath: string; cachePath: string; 
coveragePath: string; isCI: boolean; githubHostname: string; githubOrg: string; githubRepo: string; localPublicPath: string; ... 7 more ...; 
getPathWithoutRepoDirectory: (pathToShorten: string) => string; }': buildPath, babelReactIntlPath, cachePath, coveragePath, and 12 more.

Workarounds:

This takes away the nice typing mocked provides:

const mockFoo = mocked(foo);
mockFoo.mockReturnValue(({
  valueNeededForTest: 'string'
} as unknown) as ReturnType<foo>);

This isn't always possible, as running the original function can have side effects, or return something that can't be easily merged like a promise, and jest.requireActual's return type is any so it doesn't even solve the original problem.

const mockFoo = mocked(foo);
mockFoo.mockReturnValue({
  ...jest.requireActual('./foo'),
  valueNeededForTest: 'string'
});

Preferred behavior :

A new function called partialMocked (maybe there's a better name?) that allows returnType/resolvedValue/etc to be a deep partial of what it normally returns.

const mockFoo = partialMocked(foo);
mockFoo.mockReturnValue({
  valueNeededForTest: 'string' // TypeScript happy, developer happy!
});

Example implementation :

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends Array<infer U>
        ? Array<DeepPartial<U>>
        : T[P] extends ReadonlyArray<infer U>
            ? ReadonlyArray<DeepPartial<U>>
            : DeepPartial<T[P]>
};

type PartialReturnType<T extends MockableFunction> = 
    ReturnType<T> extends Promise<infer U>
        ? Promise<DeepPartial<U>>
        : ReturnType<T> extends Array<infer P>
            ? Array<DeepPartial<P>>
            : DeepPartial<ReturnType<T>>;

interface MockWithArgs<T extends MockableFunction> extends jest.MockInstance<PartialReturnType<T>, ArgumentsOf<T>> {
    new (...args: ConstructorArgumentsOf<T>): T;
    (...args: ArgumentsOf<T>): PartialReturnType<T>;
}

This fixed the issues I was having. I'm still fairly new with TypeScript, so I'm not sure if this is a complete solution or the right way to do it, so that's why I made this ticket instead of starting with a pull request.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions