In our Angular application, we are using services generated from API and we're facing issues with the unit tests for components or custom services that utilize them.
// Generated code
getCurrentConnection(observe?: 'body', reportProgress?: boolean, options?: {
httpHeaderAccept?: '*/*';
}): Observable<ConnectionResultDto>;
getCurrentConnection(observe?: 'response', reportProgress?: boolean, options?: {
httpHeaderAccept?: '*/*';
}): Observable<HttpResponse<ConnectionResultDto>>;
getCurrentConnection(observe?: 'events', reportProgress?: boolean, options?: {
httpHeaderAccept?: '*/*';
}): Observable<HttpEvent<ConnectionResultDto>>;
Usage in the custom made service
public getConnection(): Observable<ConnectionResultDto> {
return this.connectionServiceAPI.getCurrentConnection('body').pipe(tap(res => this.updateConnectionStatus(res)));
}
And an attempt to mock it in unit test (we are using Jest + Spectator + Ng-Mocks)
describe('ConnectionService', () => {
let spectator: SpectatorHttp<ConnectionService>;
const createHttp = createHttpFactory({
service: ConnectionService,
providers: [
MockProvider(ConnectionControllerService, {
getCurrentConnection: ('body') => of(CONNECTION_MOCK) // this throws error displayed below
}),
],
});
And this is the error we get (VS Code underlined)
No overload matches this call.
Overload 1 of 3, '(instance: AnyType<ConnectionControllerService>, overrides?: Partial<ConnectionControllerService>): FactoryProvider', gave the following error.
Type 'string' is not assignable to type '{ (observe?: "body", reportProgress?: boolean, options?: { httpHeaderAccept?: "*/*"; }): Observable<ConnectionResultDto>; (observe?: "response", reportProgress?: boolean, options?: { ...; }): Observable<...>; (observe?: "events", reportProgress?: boolean, options?: { ...; }): Observable<...>; }'.
Overload 2 of 3, '(provider: string | InjectionToken<{ getCurrentConnection: string; }>, useValue?: Partial<{ getCurrentConnection: string; }>): FactoryProvider', gave the following error.
Argument of type 'typeof ConnectionControllerService' is not assignable to parameter of type 'string | InjectionToken<{ getCurrentConnection: string; }>'.
Type 'typeof ConnectionControllerService' is missing the following properties from type 'InjectionToken<{ getCurrentConnection: string; }>': _desc, ɵprov
Overload 3 of 3, '(provider: string, useValue?: Partial<{ getCurrentConnection: string; }>): FactoryProvider', gave the following error.
Argument of type 'typeof ConnectionControllerService' is not assignable to parameter of type 'string'.ts(2769)
connectionController.service.d.ts(45, 5): The expected type comes from property 'getCurrentConnection' which is declared here on type 'Partial<ConnectionControllerService>'
(method) getCurrentConnection?(observe?: "body", reportProgress?: boolean, options?: {
httpHeaderAccept?: "*/*";
}): Observable<ConnectionResultDto> (+2 overloads)
Any idea how can we mock it correctly? I tried specifying every optional parameter of the getCurrentConnection
method but with no effect. Mocking without overriding methods will fail the unit tests because of the pipe
operator on the undefined results.
The problem here is that typescript aggregates all parameters and all return types. Therefore, its mock should also follow the aggregated type.
In this case, (_: 'body') => of(CONNECTION_MOCK)
is just 1 of 3.
Typescript expects _
to be 'body' | 'response' | 'events'
, and the same happens with the return type: all 3 should be respected.
All that leads to the point that it's simpler to use as never
, because it's hard to implement the desired callback.
MockProvider(ConnectionControllerService, {
getCurrentConnection: (_: 'body') => of(CONNECTION_MOCK),
} as never); // as never
or weaker options
// jasmine
MockProvider(ConnectionControllerService, {
getCurrentConnection: jasmine
.createSpy()
.and.returnValue(of(CONNECTION_MOCK)),
});
// jest
MockProvider(ConnectionControllerService, {
getCurrentConnection: jest
.fn()
.mockReturnValue(of(CONNECTION_MOCK)),
});