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;
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';
Then, in the beforeEach
, providers
the services are used like this ...
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ ... ],
declarations: [ ... ],
providers: [
{ provide: ApiHandlerService, useValue: MockApiHandlerService }
]
})
.compileComponents();
});
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));
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;
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;
This pattern then allowed me to clear the calls before each test run ...
beforeEach(() => {
MockApiHandlerService._reset();
});
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.