Open FreifeldRoyi opened 4 years ago
I ran into the same issue when mocking Fingerprintjs2
; Fingerprint2.get.mockImplementation
expected the one argument signature, but I needed to implement the two argument version. This is what it took to make it work (the commented lines are the important ones), perhaps this could be clearly documented or streamlined somehow?
import {MockProxy, mockReset} from 'jest-mock-extended';
import Fingerprint2 from 'fingerprintjs2';
jest.mock('fingerprintjs2');
type FingerprintCallback = (data: Fingerprint2.Component[]) => void;
// Signature for `Fingerprint2.get` to mock (we want the one with two args)
type FingerprintGetWithTwoArgs = (
opt: Fingerprint2.Options,
callback: FingerprintCallback
) => void;
const MockFingerprint2 = Fingerprint2 as MockProxy<typeof Fingerprint2>;
// Reset the mock before each test to remove any custom implementations
beforeEach(() => mockReset(MockFingerprint2));
it('passes generated fingerprint to callback', () => {
const testData = [{key: 'foo', value: 'bar'}];
// Cast the mocked function to the signature we want and assign our own `jest.fn`
(Fingerprint2.get as FingerprintGetWithTwoArgs) = jest.fn(
(opt: Fingerprint2.Options, callback: FingerprintCallback) => {
callback(testData);
},
);
expect.assertions(1);
Fingerprint2.get({}, (data: Fingerprint2.Component[]) => {
// Ta-da! We got the two-argument version to pass our test data back
expect(data).toBe(testData);
});
});
The gist is that you cast your mocked function as the signature you want, then assign it as a jest.fn
or something
I should mention that I only realized this because the type of Fingerprint2.get
was this beast:
CalledWithMock<void, [(components: Component[]) => void]> & {
(options: Options, callback: (components: Component[]) => void): void;
(callback: (components: Component[]) => void): void;
}
You can see the CalledWithMock
that provides mockImplementation
is for the one argument signature, but the type intersects with the two-argument function so you can cast to that
Wow... I tried to add undefined
as the second parameter and it worked. But I feel like it should be clear in tests that the the tested code is calling one function and not the other, and currently it is not.
I get the same issue
These workarounds are very verbose and hard-to-read, and somewhat defeat the purpose of using jest-mock-extended. Since most of the mocks we use are for OpenAPI generated services which make generous use of overloading, this issue negates much of the value of jest-mock-extended in our projects. Worse, there is no decent documentation about how to use this workaround with mockReturnValue, etc.
@tpischke-bedag did you switch to a better lib?
We are still using the library, but only in a very limited fashion. We've mostly switched to implementing our own simple stubs to avoid these issues.
are we still not able to use jest for overloaded methods? can someone help with a demo code for any work-around?
@iamprathamesh
SpotifyAlbumsService
export class SpotifyAlbumsService {
public getAlbum(id: string, adapt: false): Promise<SdkAlbum>
public getAlbum(id: string, adapt: true): Promise<Album>
async getAlbum(id: string, adapt = false) {
this.spotifySdk = SpotifyApi.withClientCredentials(
this.configService.get<string>(Environment.SPOTIFY_CLIENT_ID)!,
this.configService.get<string>(Environment.SPOTIFY_CLIENT_SECRET)!
)
const data = await this.spotifySdk.albums.get(id)
return adapt ? this.adaptersService.albums.adapt(data) : data
}
}
test
test('should create album from external id', async () => {
const getAlbumSpy = (
jest.spyOn(spotifyAlbumsService, 'getAlbum') as unknown as MockInstance<
[id: string, adapt: false],
Promise<SdkAlbum>
>
).mockResolvedValue(sdkAlbumMock)
})
I wish there is a better way to do that...
I'm trying to mock an overloaded function. More precisely NestJS' ConfigService.get but mockService.get.calledWith expects the 2 param method while I need the one param.
How to resolve this issue?