As we studied in detail in Chapter 5, Angular Services and the Singleton Pattern, the service that works as a repository of business rules in an Angular application. Consequently, it is crucial for us to develop unit tests for these services. In this section, we will focus on the ExerciseSetsService service to illustrate the Angular unit testing techniques in our project. Let’s begin.
In the exercise-sets.service.spec.ts test file, let’s start by fixing the tests automatically created by the Angular CLI that are not running correctly:
import { TestBed } from ‘@angular/core/testing’;
import { ExerciseSetsService } from ‘./exercise-sets.service’;
import { HttpClientTestingModule } from ‘@angular/common/http/testing’;
fdescribe(‘ExerciseSetsService’, () => {
let service: ExerciseSetsService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
service = TestBed.inject(ExerciseSetsService);
httpMock = TestBed.inject(HttpTestingController);
});
it(‘should be created’, () => {
expect(service).toBeTruthy();
});
});
As we want to work on service testing, at this time, we replace the describe function with the fdescribe function, so the Karma test runner will only execute this test case. The fdescribe feature is also available for isolating a specific test, in this case replacing the it function with the fit function. To fix the error identified by the Angular compiler, we import the ‘HttpClientTestingModule’ module in the TestBed component.
We need to understand how Karma, Jasmine, and Angular work together to run tests. Before each test case is defined in it functions, Angular sets up an isolated environment for the tests. This environment has virtually no module configuration at first, as your real application has, and the TestBed component comes into play, where we configure the minimum necessary dependencies for your test to run.
In this service, as it depends on HttpClient to perform HTTP requests, we need to import the HttpClientModule module to have this dependency. You might be wondering, “But here you are using HttpClientTestingModule. Is this correct?” As we will see in the following code, not only will we want to use HttpClient but we will also need to simulate HTTP calls, and to make this task easier, the Angular team has prepared a specific module for this type of testing.
With our basic “should be created” test case in place, let’s test the methods of the class:
it(‘should use the method getInitialList to return the list of entries’, fakeAsync(() => {
const fakeBody: ExerciseSetListAPI = {
hasNext: false,
items: [
{
id: ‘1’,
date: new Date(),
exercise: ‘Deadlift’,
reps: 15,
sets: 4,
},
],
};
service.getInitialList().subscribe((response) => {
expect(response).toEqual(fakeBody.items);
});
const request = httpMock.expectOne((req) => {
return req.method === ‘GET’;
});
request.flush(fakeBody);
tick();
}));
As you can see from the preceding code, this service is designed to handle requests related to gym diary entries. In the initial method, getInitialList, our objective is to verify whether the service accurately initiates an HTTP request to the backend using the GET method. By creating a new case with the it function in the first parameter, we place a description of the test case that will be important for viewing during test execution. The test function, unlike the “should be created” test case, is contained within the fakeAsync function created by the Angular team to facilitate the testing of asynchronous methods, such as an HTTP request. Inside the function, we begin to assemble our test. Here, we need to define what the structure of a unit test looks like.
A unit test consists of three parts:
- The test setup, where we prepare all the elements for the tests to take place
- The execution of the method to be executed
- The test assertion, where we compare the execution result with the expected return