如何用玩笑模拟组件中使用的“嵌套”角度服务

Posted

技术标签:

【中文标题】如何用玩笑模拟组件中使用的“嵌套”角度服务【英文标题】:How to mock a "nested" angular service used in a component with jest 【发布时间】:2019-10-22 08:12:41 【问题描述】:

我想用 Jest (^v24.8.0) 为 Angular (~v7.2.0) 组件编写单元测试。

这是组件,它通过this.api.manageUser.getUsersStats() 使用嵌套服务,我想检查它是否被调用并模拟结果。所以最好的办法是能够在“mocks”文件夹中编写“全局”/“手动”模拟。

我已经尝试了很多东西,但都没有达到预期效果。

我真的希望有人可以帮助我!

// 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
    )
  

这是我的服务:

// 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
    
    /**
     * @throwsApiError
     * @throwsError
     */
    get<T>(path: string): Observable<T> 
      return this.http.get<T>(environment.apiBaseUrl + path)
    

这是规范文件:

/// <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();
  )
)

对于“方法 3”,这里是“手动”-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);
  

【问题讨论】:

您可以添加您的spec 文件吗?我认为答案在于您 provide 并为此特定测试设置测试模块。 @Bjorn'Bjeaurn'S 刚刚做了 :) 你说“没有一个按预期工作”,你能添加你看到的内容吗?您看到了什么错误? 【参考方案1】:

您可以尝试使用ng-mocks 及其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();
  );
);

【讨论】:

以上是关于如何用玩笑模拟组件中使用的“嵌套”角度服务的主要内容,如果未能解决你的问题,请参考以下文章

开玩笑地模拟 useDispatch 并在功能组件中使用该调度操作来测试参数

如何用玩笑测试 Next.js 的 getServerSideProps

如何用玩笑嘲笑S3?

用玩笑进行单元测试时,如何以角度模拟 ResizeObserver polyfill?

在玩笑中模拟功能组件会引发“无效的变量访问”错误

开玩笑的预期模拟未调用(redux 组件)