Aliasger Jaffer
Aliasger Jaffer's Blog

Aliasger Jaffer's Blog

Mocking dependencies in Angular

Mocking dependencies in Angular

Don't create your own object to mock the dependency, use jasmine

Aliasger Jaffer's photo
Aliasger Jaffer
·Sep 30, 2021·

4 min read

Subscribe to my newsletter and never miss my upcoming articles

It is quite common to have a component or a service that depends on another service. For example, a Directory service may need to get data from a Database service.

Here is an example of such a Directory service:

import {Injectable} from '@angular/core';
import {DatabaseService} from './database.service';

@Injectable()
export class DirectoryService {
  constructor(private dbService: DatabaseService) {}

  contacts(): string[] {
    return this.dbService.getContacts();
  }
}

In the code above, we see that the Directory service has a constructor with the DatabaseService injected to it by Angular. As such, whenever the Directory service is instantiated (i.e, Angular DI provides us an instance of the Directory service), it will also have an instance of the Database service provided into the constructor. Since we use the private keyword in the constructor, a local variable named dbService will be created and assigned to the value of the DatabaseService.

We know that Unit Tests should run in isolation. By that, I mean that when we test the Directory service, we should not be injecting the actual Database service into it. Instead, we should be creating a fake Database service that we have control over.

Why should we mock the dependency?

There are several reasons why we should not inject the actual dependency. Let us consider several (from many) scenarios of what could happen if we instantiated an actual instance of the Database service:

  1. What would happen if the database service returns millions of contacts? This could slow down the test and potentially cause memory issues!
  2. What would happen if the Database service was making an actual HTTP call behind the scenes? Every time we run our test, we would spamming our network.
  3. How will we know what the response of the getContacts call be? To run our expectations, we need to know what to expect and what to match it against. Running an actual instance means no guarantee on what the response will be (or may change in the future).

Steps to testing with a dependency

  1. Create a mock of the dependency (in our case, the Database service).
  2. Provide this mock database to our module definition within the TestBed's configureTestingModule function.
  3. Create the individual spec test (the 'it' functions)

1. Mocking the database service

I have come across many different ways developers have mocked dependencies in Angular. For example, I have seen the following working code:

// Example 1 - verbose method of mocking  
const mockDb = {
    getContacts: () => ['A', 'B']
  };

This works, but there is a better approach using the jasmine framework.

  const mockDb = jasmine.createSpyObj('DatabaseService',['getContacts']);

The statement above gives us a mock of our service with a getContacts method inside it.

You may be asking yourself a question now. What does getContacts return? We will answer this question shortly.

Providing the mock database

Now that we have the mock database created, we can provide it to the TestBed, and the way to do it is with the configureTestingModule method.

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      DirectoryService,
      {
        provide: DatabaseService,
        useValue: mockDb
      }]
  });
}

This makes sure that our TestBed gives us the mockDb instance of the DatabaseService instead of the actual instance of the Database service.

Create the spec to test

Now that the TestBed has been configured, we should be able to create our tests. This brings us to the ultimate question - what does the getContacts method from our mockDb return?

The answer is quite simple - whatever you want it to return. Of course, you want it to return the data you expect. In our case, we want it to return an array of string.

  it('should return contacts', () => {
    const testContacts = ['TEST 1', 'TEST 2'];
    mockDb.getContacts.and.returnValue(testContacts);
    expect(directoryService.contacts()).toEqual(testContacts);
  });

Noticed the line that creates the return value for our getContacts method? We have full control on what our Database service returns and we know that the return value from that call should match the return of our DirectoryService's contacts method.

Now that we have understood how to test dependencies, a very interesting question arises. How do we test HTTP calls? What if instead of the DatabaseService, we were injecting HttpClient?

Let's do that on my next post.

 
Share this