How to mock an @injected service when testing an angular 2 component?

12,057

As stated in the comments, Angular needs whatever token is in the @Inject(...) to be the same as what is given for the provide property when setting up the service with Angular's DI. This also means that the injection token should be exported outside the module for others to use the @Inject() syntax.

If the service is provided like this:

@NgModule({
    providers: [
        { provide: SERVICE_TOKEN, useClass: Service }
    ]
})

Then the @Inject() should be set up like this (using the same injection token):

constructor(@Inject(SERVICE_TOKEN) private service: Service) {
}

So, in your test you mock it out via (again, using the same injection token):

beforeEach(async(() => {
    TestBed.configureTestingModule({
        providers: [
            { provide: SOME_TOKEN, useValue: mockService }
        ]
    })
    .compileComponents();
}));

For more information on injection tokens, refer to the Angular docs.

Share:
12,057

Related videos on Youtube

StuperUser
Author by

StuperUser

A full stack web app developer most recently using Angular, WebAPI, Dapper. 1.5 years experience in QA and an ISTQB/ISEB Foundation Certificate in Software Testing. BA in Philosophy and Computing from University of Kent. #SOreadytohelp

Updated on June 04, 2022

Comments

  • StuperUser
    StuperUser almost 2 years

    I have a component with the signature:

    constructor(private loremApiService: LoremApiService,
          private ipsumService: IpsumService,
          private dolorService: DolorService,
          @Inject('sitService') private sitService: library.service.Service) {
    }
    

    The spec file for the component is set up with:

    let component: PowerBiReportComponent;
    let fixture: ComponentFixture<TestingComponent>;
    const mockLoremApi = { methodThatIsCalled: () => {} };
    const mockIpsumService = { };
    const mockSitService = { };
    
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [ TestingComponent ],
            schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
            providers: [
                { provide: LoremApiService, useValue: mockLoremApi },
                { provide: IpsumService, useValue: mockIpsumService },
                UnmockedService,
                { provide: library.service.Service, useValue: mockSitService }
            ]
        })
        .compileComponents();
    }));
    
    beforeEach(() => {
        fixture = TestBed.createComponent(TestingComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });
    

    However, the provider is not used, due to the @Inject decorator, the test is failing on run with:

    Error: StaticInjectorError(DynamicTestModule)[sitService]: 
      StaticInjectorError(Platform: core)[sitService]: 
      NullInjectorError: No provider for sitService!
    

    How do I force the TestingModule to use the mockSitService despite it being @Injected in the component?

    • Daniel W Strimpel
      Daniel W Strimpel about 6 years
      Did you try to do: { provide: 'sitService', useValue: mockSitService }?
    • StuperUser
      StuperUser about 6 years
      @DanielWStrimpel Unfortunately the unanonymised service is imported with import * as library from 'sit-library';, so I can't be that specific. Also, tragically, the actual service Type is named Service
    • Daniel W Strimpel
      Daniel W Strimpel about 6 years
      Whatever is in the @Inject(...) should be what is given as the provide property. Newer versions of Angular require (or strongly recommends?) these to be injection tokens angular.io/guide/dependency-injection#injectiontoken
    • StuperUser
      StuperUser about 6 years
      @DanielWStrimpel Ahhh, I've changed the provide object in the test to match the injectionToken in the component's parent module. Now it's erroring with an [object ErrorEvent] thrown which may be part of the actual test code failing due to an unimplemented mock. This might fix the issue
    • Fateh Mohamed
      Fateh Mohamed about 6 years
      check my response here stackoverflow.com/a/49842183/4399281 you can find an example of mocking a service
    • StuperUser
      StuperUser about 6 years
      @DanielWStrimpel If you add ensuring that the injection tokens match in the component's module and the TestModule as the configureTestingModule as an answer, I'll accept that
  • EHorodyski
    EHorodyski over 5 years
    What if you have a module, that uses forRoot for it's injectable tokens and you want to test one of the services inside of it that utilizes the injected token? MyModule.forRoot(MyModuleConstants) has a service MyModuleService and has constructor(@Inject(MyInjectionToken) constants)
  • Gao Shenghan
    Gao Shenghan almost 4 years
    useClass instead of useValue?
  • Daniel W Strimpel
    Daniel W Strimpel almost 4 years
    @GaoShenghan the syntax depends on what you are providing. When setting it up in the module it is using useClass so that Angular creates the instance for you. In the test, the OP has variables that are instances of the stubbed services, so you would use the useValue version of the provide syntax for those.