0 votes
in AngularJS Packaging and Testing by
How do you mock a service to inject in a unit test?

1 Answer

0 votes
by
Below are some of the methods we can use to mock a service to inject in a unit test

a.) Resolving via TestBed

The TestBed acts as a dummy Angular Module and we can configure it like one including with a set of providers like so:

TestBed.configureTestingModule({

  providers: [AuthService]

});

We can then ask the TestBed to resolve a token into a dependency using it’s internal injector, like so:

testBedService = TestBed.get(AuthService);

If most of our test specs need the same dependency mocked the same way we can resolve it once in the beforeEach function and mock it it there.

b.) Resolving via the inject function

it('Service injected via inject(...) and TestBed.get(...) should be the same instance',

    inject([AuthService], (injectService: AuthService) => {

      expect(injectService).toBe(testBedService);

    })

);

The inject function wraps the test spec function but lets us also inject dependencies using the parent injector in the TestBed.

inject(

  [token1, token2, token2],

  (dep1, dep2, dep3) => { }

)

The first param is an array of tokens we want to resolve dependencies for, the second parameter is a function whose arguments are the resolved dependencies.

Using the inject function:

Makes it clear what dependencies each spec function uses.

If each test spec requires different mocks and spys this is a better solution that resolving it once per test suite.

c.) Overriding the components providers

Before we create a component via the TestBed we can override it’s providers. Lets imagine we have a mock AuthService like so:

class MockAuthService extends AuthService {

  isAuthenticated() {

    return 'Mocked';

  }

}

We can override the components providers to use this mocked AuthService like so.

TestBed.overrideComponent(

    LoginComponent,

    {set: {providers: [{provide: AuthService, useClass: MockAuthService}]}}

);

The syntax is pretty specific, it’s called a MetaDataOverride and it can have the properties set, add and remove. We use set to completely replace the providers array with the values we’ve set.

d.) Resolving via the component injector

When the component is created since it has it’s own injector it will resolve the AuthService itself and not forward the request to it’s parent TestBed injector.

If we wanted to get the same instance of dependency that was passed to the component constructor we need to resolve using the component injector, we can do that through the component fixture like so:

componentService = fixture.debugElement.injector.get(AuthService);

login.component.spec.ts

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);

        })

    );

    it('Service injected via component should be and instance of MockAuthService', () => {

        expect(componentService instanceof MockAuthService).toBeTruthy();

    });

});

Related questions

0 votes
asked Aug 19, 2021 in AngularJS Packaging and Testing by SakshiSharma
0 votes
asked May 5, 2020 in Testing by Robindeniel
...