#native_company# #native_desc#
#native_cta#

Testing Dependency Injection


Learning Objectives

  • Know how we can configure the injectors for testing in our Angular application.

  • Know the various methods we can use to resolve tokens for testing.

Resolving via TestBed

This is how we’ve injected dependencies so far in this section.

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 its 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.

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.

We use it like so:

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 spies this is a better solution than resolving it once per test suite.

Important

This will eventually move to becoming a function decorator like so:

@Inject (dep1: Token1, dep2: Token2) => { ... }

Overriding the Component’s Providers

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

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

We can override the component’s 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.

Resolving via the Component’s Injector

Now our component has been configured with its own providers it will therefore have a child injector.

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

If we wanted to get the same instance of a dependency that was passed to the component’s constructor we would need to resolve it using the component’s injector. We can do that through the component fixture like so:

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

The above code resolves the token using the component’s child injector.

Summary

We can resolve dependencies in our tests using a number of methods.

We can resolve using the the test bed itself, usually in the beforeEach function and store the resolved dependencies for use in our test specs.

We can resolve using the inject function at the start of each test spec.

We can also override the default providers for our components using the TestBed.

We can then also use the component’s child injector to resolve tokens.

Listing

Listing 1. login.component.spec.ts
/* tslint:disable:no-unused-variable */
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();
    });
});

Caught a mistake or want to contribute to the book? Edit this page on GitHub!



Advanced JavaScript

This unique course teaches you advanced JavaScript knowledge through a series of interview questions. Bring your JavaScript to the 2021's today.

Level up your JavaScript now!