Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a "nested" angular service used in a component with jest

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);
  }
}
like image 978
dnepro Avatar asked Jun 06 '19 19:06

dnepro


People also ask

Which method is used to mock the service method response in Angular?

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.

How do you mock another component Jest?

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.

What is __ mocks __ in Jest?

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.


1 Answers

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();
  });
});
like image 84
satanTime Avatar answered Sep 29 '22 09:09

satanTime