Angular: Unit Test Mock Service

bob.ts - Aug 16 '21 - - Dev Community

So, I needed to mock a service.

In particular, I wanted to mock the API Handler Service. This service is the abstraction layer I use to interact with the back-end. There's often a lot going on here.

When testing other code, I wanted to mock the calls and data responses to ensure stability.

Mocking Tools

While there are a lot of tools that can mock or replace an HTTP server, what I wanted to do was mock THIS service so that other code had consistent data coming back.

Starting Code

I started with something like this ...

// createSpyObj returns the attached OBJECT,
// if a function is attached, that is what is
// returned (not executed).
let MockApiHandlerService = jasmine.createSpyObj('ApiHandlerService', {
  ...
  getChartData: Promise.resolve(chartData),
  getEventDetail: Promise.resolve(eventDetail),
  getEventSummary: Promise.resolve(eventSummary),
  ...
});

export default MockApiHandlerService;
Enter fullscreen mode Exit fullscreen mode

Yes, there are a lot of other functions I am not covering. These show the basics. The chartData, eventDetail, and eventSummary are data points listed higher in the file that I could use as mock data.

This code worked great.

And YES, I left the comment in ... after creating functions to execute. I have this on any jasmine.createSpyObject in my code to remind me.

These values are what are returned ... regardless of what is passed to the function.

Tying this into Jasmine

First, the actual and mock service need imported ...

import { ApiHandlerService } from '@core/services/api-handler.service';
import MockApiHandlerService from '@shared/_spec-tools/mock-api-handler.service';
Enter fullscreen mode Exit fullscreen mode

Then, in the beforeEach, providers the services are used like this ...

beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ ... ],
      declarations: [ ... ],
      providers: [
        { provide: ApiHandlerService, useValue: MockApiHandlerService }
      ]
    })
    .compileComponents();
  });
Enter fullscreen mode Exit fullscreen mode

Issues

Changing the Value

The first issue I has was that I wanted different data returned on one of the functions.

What I found was that I was changing the value returned for other tests doing something like this.

MockApiHandlerService.getUsers.and.returnValue(Promise.resolve(null));
Enter fullscreen mode Exit fullscreen mode

What I wound up having to do to correct this was to capture the "old" value and replace it after the test expects ...

const oldValue = MockApiHandlerService.getUsers;
MockApiHandlerService.getUsers.and.returnValue(Promise.resolve(null));

...
MockApiHandlerService.getUsers = oldValue;
Enter fullscreen mode Exit fullscreen mode

Resetting the Calls

Additionally, I ran into issues resetting calls and checking to see the number of times a service function was called in a test.

Initially, I was clearing them per test, but after the first time I implemented something like this ...

// createSpyObj returns the attached OBJECT,
// if a function is attached, that is what is
// returned (not executed).
let MockApiHandlerService = jasmine.createSpyObj('ApiHandlerService', {
  ...
  getChartData: Promise.resolve(chartData),
  getEventDetail: Promise.resolve(eventDetail),
  getEventSummary: Promise.resolve(eventSummary),
  ...
});

MockApiHandlerService._reset = () => {
  MockApiHandlerService.getChartData.calls.reset();
  MockApiHandlerService.getEventDetail.calls.reset();
  MockApiHandlerService.getEventSummary.calls.reset();
};

export default MockApiHandlerService;
Enter fullscreen mode Exit fullscreen mode

This pattern then allowed me to clear the calls before each test run ...

beforeEach(() => {
  MockApiHandlerService._reset();
});
Enter fullscreen mode Exit fullscreen mode

Summary

Throughout this process, I learned a lot of things about mocking a service. In particular, one that was used as frequently as this one throughout the application.

When testing other code, I wanted to mock the calls and data responses to ensure stability. With the mock service above, I was able to attain all the stated goals.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .