angulartypescriptjestjsng-mocks

Mocking generated API service


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.


Solution

  • 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)),
    });