Search code examples
typescriptkarma-jasmineangular-testangular17angular-oauth2-oidc

Authentication Library Config Type Only Import Causes Reference Error


I have an authentication library designed to keep auth stuff consistent across various webapps, inspired by the angular-oauth2-oidc library. I am running into an issue when testing my app, however. In the implemented auth service (employee-auth.service.ts), which extends the library auth service, I get a "ReferenceError: AuthLibConfig is not defined."

I finally discovered that the issue was that the import of the library config, at the behest of eslint, is type only. I am looking for some insight on why a type only import is not sufficient and why it is causing this reference error.

This does not work, but satisfies eslint

import { AUTH_CONFIG_TOKEN, type AuthLibConfig, AuthLibService } from '../auth-lib';

This does work, but yields an eslint error

import { AUTH_CONFIG_TOKEN, AuthLibConfig, AuthLibService } from '../auth-lib';

Here's a stackblitz and directions to reproduce:

https://stackblitz.com/edit/stackblitz-starters-n8o79p

Wait for Stackblitz container to install deps

  • Ctrl+C the served application
  • Run ng test
  • Expected behavior
  • I expect the test to pass without reference error. type can be removed from the import statement to produce a passing test.

I am using ng-mocks; I was worried about it being a bug with their MockBuilder, but I stripped it all out for a stock angular unit test and it didn't make a difference.

I've tried providing the AuthLibConfig in the test and creating an instance of the AuthLibConfig directly in the test, but it still remains undefined.


Solution

  • So, TS understands AuthLibConfig is only used as a type in that specific file, but we know AuthLibConfig is more than just a type as per its usage across the project. So we cannot export it as type

    If we import it as type, AuthLibConfig references in that file are not processed at test runtime and it is disconnected from its actual bundled definition, thereby resulting in test failure.

    It is like a tug of war between TS focusing on the single file vs Angular focusing on the whole project, and neither is willing to give up how they function without a fight.


    Option 1: Relax the TS eslint rules for consistent-type-imports but it can cause issues if future code is not handled properly.

    (or)

    Option 2: Rewrite the injected services using inject method as below,

    FROM

    ...
    
    export class EmployeeAuthService extends AuthLibService<Employee> {
      public constructor(
        @Inject(HttpClient) private readonly http: HttpClient,
        @Inject(OAuthService) oauth: OAuthService,
        @Inject(AUTH_CONFIG_TOKEN) config: AuthLibConfig
      ) {
        super(oauth, config);
      }
      ...
    }
    

    TO

    import { inject } from '@angular/core'
    ...
    
    export class EmployeeAuthService extends AuthLibService<Employee> {
      private readonly http = inject(HttpClient)
      oauth = inject(OAuthService)
      config = inject(AUTH_CONFIG_TOKEN)
      ...
    }
    

    This will remove the need for using AuthLibConfig as type. But you may have to change it on all files, for better consistency/readability.