Angular Unit Test for Singleton Service

Viewed 1009

I am working on a unit test in angular regarding singleton service. I have a UserService that is provided in the root module is a singleton in the application and I want to write a unit test to check if the instance of UserService is the same throughout the application. I can do something like below but it would check component and Testbed instance. Is there any way I can write one unit test for all the feature modules to check if we have the same instance? Appreciate any help.

import { TestBed, ComponentFixture, inject } from '@angular/core/testing';
import { LoginComponent } from './login.component';
import { AuthService } from "./auth.service";

class MockAuthService extends AuthService {
   isAuthenticated() {
       return 'Mocked';
   }
}

describe('Component: Login', () => {

let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let testBedService: AuthService;
let componentService: AuthService;

beforeEach(() => {

    // refine the test module by declaring the test component
    TestBed.configureTestingModule({
        declarations: [LoginComponent],
        providers: [AuthService]
    });

    // Configure the component with another set of Providers
    TestBed.overrideComponent(
        LoginComponent,
        { set: { providers: [{ provide: AuthService, useClass: MockAuthService }] } }
    );

    // create component and test fixture
    fixture = TestBed.createComponent(LoginComponent);

    // get test component from the fixture
    component = fixture.componentInstance;

    // AuthService provided to the TestBed
    testBedService = TestBed.get(AuthService);

    // AuthService provided by Component, (should return MockAuthService)
    componentService = fixture.debugElement.injector.get(AuthService);
});

it('Service injected via inject(...) and TestBed.get(...) should be the same instance',
    inject([AuthService], (injectService: AuthService) => {
        expect(injectService).toBe(testBedService);
    })
);

});
1 Answers

I feel this falls into the large category of "don't test the framework".

If you decorate the service type with an @Injectable({ providedIn: 'root' }) decorator, you are guaranteed that every component or service that requires an instance will get the same instance. Since Angular 6 (or 5? I don't remember) you shouldn't usually add providers for services in your modules. The default behavior using providedIn corresponds to your requirement. See this for reference: https://angular.io/guide/providers.

The guarantee that every provided service is resolved as a singleton comes from the Unit tests of the Injector, and shouldn't be guaranteed by yours, both in principle and in practice: when running unit tests you are not using the same Dependency Injection infrastructure than when running the application! Your test would be, therefore, pointless: you would test the TestBed, and not the runtime injector.

And you cannot guarantee that someone in the codebase will add a { provide: AuthService, useClass: AuthService } and instantiate another AuthService.

I don't know about your exact requirements, but:

  • if you need to "know" that there is only one instance built, go with providedIn: 'root', remove every provider in every feature module, and you're good to go. The default behavior fits your requirement.
  • if you would like to be extra sure that the constructor runs only one per application, and you're fine with runtime errors (you can't catch this at compile time, for the reasons above) you can add an assert:
@Injectable({ providedIn: 'root' })
export class AuthService {
  private static initialized = false;

  constructor() {
    if (isDevMode() && AuthService.initialized) {
      throw new Error('AuthService: instance already initialized');
    }

    AuthService.initialized = true;
  }

}

Just, don't unit test it.

Related