I want to write a unit test for an Angular (~v7.2.0) Component with Jest (^v24.8.0).
This is the component, it's using the nested service via this.api.manageUser.getUsersStats()
and i want to check if this is ever called and mock the result. So best thing would be to able to write a "global" / "manual" mock in a "mocks"-folder.
I already tried a lot of things, but none works as expected.
I really hope somebody can help me!
// users.component
import { TstApiService } from '../../services/TstApi/TstApi.service';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.sass']
})
export class UsersComponent implements OnInit, OnDestroy {
constructor(
// ...
private api: TstApiService
) { }
ngOnInit() {
this.getUsersStats();
}
private getUsersStats() {
this.api.manageUser.getUsersStats().pipe(take(1)).subscribe(userStats => {
/* ... */ = userStats.allUsers
})
}
}
This is my Service:
// TstApi.service
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class TstApiService {
private http: TstHttp
manageUser: ManageUser
constructor(private httpClient: HttpClient) {
this.http = new TstHttp(this.httpClient)
this.manageUser = new ManageUser(this.http)
}
}
class ManageUser {
constructor(private http: TstHttp) {}
getUsersStats(): Observable<TUserStats> { //<-- this is the function used by the component
return this.http.get('/manage/user/stats')
}
}
class TstHttp {
private apiUrl: string
constructor(private http: HttpClient) {
this.apiUrl = environment.apiBaseUrl
}
/**
* @throws{ApiError}
* @throws{Error}
*/
get<T>(path: string): Observable<T> {
return this.http.get<T>(environment.apiBaseUrl + path)
}
}
This is the spec file:
/// <reference types='jest'/>
import { TestBed, ComponentFixture, inject } from '@angular/core/testing';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';
import { UsersComponent } from './users.component';
import { UsersService } from './users.service';
import { TstApiService } from "src/app/services/TstApi/TstApi.service";
// Approach No. 3
jest.mock("src/app/services/TstApi/TstApi.service")
// Approach No. 1
// jest.mock("src/app/services/TstApi/TstApi.service", () => {
// return jest.fn().mockImplementation(() => {
// return {manageUser: () => { getUsersStats: jest.fn() }};
// })
// });
describe('Users', () => {
@Component({ selector: 'app-input', template: '' }) class InputComponent {}
let router: Router;
let component: UsersComponent;
let fixture: ComponentFixture<UsersComponent>;
const http: HttpClient = new HttpClient(null);
let apiService: TstApiService = new TstApiService(http);
const usersService: UsersService = new UsersService(apiService);
let usersServiceMock;
// let tstApiServiceMock; // Approach No. 2
let httpMock: HttpTestingController;
beforeEach(async () => {
// Approach No. 2
// tstApiServiceMock = {
// manageUser: jest.fn().mockImplementation(() => ({
// getUsersStats: jest.fn().mockImplementation(() => Promise.resolve(null))
// }))
// }
await TestBed.configureTestingModule({
declarations: [
UsersComponent,
InputComponent
],
imports: [
HttpClientTestingModule,
RouterTestingModule.withRoutes([])
],
providers: [
// TstApiService
// { provide: TstApiService, useValue: tstApiServiceMock } // Approch No. 2
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
.compileComponents()
.then(() => {
// create component and test fixture
fixture = TestBed.createComponent(UsersComponent);
router = TestBed.get(Router)
// get test component from the fixture
component = fixture.componentInstance;
});
});
beforeEach(
// Approach No. 4
inject([TstApiService, HttpTestingController], (_service, _httpMock) => {
apiService = _service;
httpMock = _httpMock;
}));
test.only('Should getUsersStats on NgInit', () => {
expect(apiService.manageUser.getUsersStats).toHaveBeenCalled();
})
})
For the "Approach No. 3", here is the "manual"-mock:
import { ManageUser, TstHttp, ErrorAlert } from './../TstApi.service';
// import { HttpClient } from '@angular/common/http';
// export const TstApiService = jest.genMockFromModule('../TstApi.service.ts');
export class TstApiService {
private http: TstHttp;
manageUser: ManageUser;
error: ErrorAlert;
constructor(private httpClient) {
this.error = new ErrorAlert();
this.http = new TstHttp(this.httpClient, this.error);
this.manageUser = new ManageUser(this.http);
}
}
Prefer spies as they are usually the best way to mock services. These standard testing techniques are great for unit testing services in isolation. However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.
To mock a React component, the most straightforward approach is to use the jest. mock function. You mock the file that exports the component and replace it with a custom implementation. Since a component is basically a function, the mock should also return a function.
Mocking Node modules If the module you are mocking is a Node module (e.g.: lodash ), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root) and will be automatically mocked. There's no need to explicitly call jest.
You can try to use ng-mocks
and its MockBuilder
.
describe('suite', () => {
const getUsersStats = jest.fn(() => EMPTY);
beforeEach(() => {
// the 2nd param should be the module of UsersComponent
return MockBuilder(UsersComponent)
.mock(TstApiService, {
// because ManageUser is provided via `new` call,
// we need to provide a mock instance manually.
manageUser: MockService(ManageUser, {
getUsersStats,
}),
});
});
it('spec', () => {
MockRender(UsersComponent);
expect(getUsersStats).toHaveBeenCalled();
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With