簡體   English   中英

使用Jasmine單元測試角度1.x組件(使用Typescript,Webpack)

[英]Unit test angular 1.x components with Jasmine (using Typescript, Webpack)

我正在使用angular 1.6,typescript,webpack,karma和jasmine編寫應用程序。 我能夠為角度服務創建單元測試,但現在我遇到了測試組件的麻煩。 在SO (1)(2)以及網上我發現了不同的例子( 像這樣 ),但沒有一個明確的指南解釋如何用上述技術集測試角1組件。

我的組件 (HeaderComponent.ts):

import {IWeatherforecast} from '../models/weather-forecast';
import WeatherSearchService from '../search/weather-search.service';
import WeatherMapperService from '../common/mapping/weatherMapper.service';


export default class HeaderComponent implements ng.IComponentOptions {
  public bindings: any;
  public controller: any;
  public controllerAs: string = 'vm';
  public templateUrl: string;
  public transclude: boolean = false;

constructor() {
    this.bindings = {
    };

    this.controller = HeaderComponentController;
    this.templateUrl = 'src/header/header.html';
    }
}

 export class HeaderComponentController {
   public searchText:string
   private weatherData : IWeatherforecast;

static $inject: Array<string> = ['weatherSearchService', 
                                 '$rootScope', 
                                 'weatherMapperService'];

     constructor(private weatherSearchService: WeatherSearchService, 
                 private $rootScope: ng.IRootScopeService, 
                 private weatherMapperService: WeatherMapperService) {
 }

 public $onInit = () => {
     this.searchText = '';
 }

 public searchCity = (searchName: string) : void => {

     this.weatherSearchService.getWeatherForecast(searchName)
         .then((weatherData : ng.IHttpPromiseCallbackArg<IWeatherforecast>) => {
             let mappedData = this.weatherMapperService.ConvertSingleWeatherForecastToDto(weatherData.data);

             sessionStorage.setItem('currentCityWeather', JSON.stringify(mappedData));

             this.$rootScope.$broadcast('weatherDataFetched', mappedData);

         })
         .catch((error:any) => console.error('An error occurred: ' + JSON.stringify(error)));
 }
}

單元測試

import * as angular from 'angular';
import 'angular-mocks';

import HeaderComponent from '../../../src/header/header.component';

describe('Header Component', () => {
  let $compile: ng.ICompileService;
  let scope: ng.IRootScopeService;
  let element: ng.IAugmentedJQuery;

  beforeEach(angular.mock.module('weather'));
  beforeEach(angular.mock.inject(function (_$compile_: ng.ICompileService, _$rootScope_: ng.IRootScopeService) {
    $compile = _$compile_;
    scope = _$rootScope_;
  }));

beforeEach(() => {
    element = $compile('<header-weather></header-weather>')(scope);
    scope.$digest();
});

對我來說不清楚如何訪問控制器類,以便測試組件業務邏輯。 我嘗試注入$ componentController,但我不斷收到錯誤“未捕獲的TypeError:無法設置未定義的屬性'mock',我認為這與未正確注入的角度模擬有關。

任何人都可以建議解決方案的方法或網站在哪里可以找到有關使用打字稿和webpack進行單元測試角度1組件的更多詳細信息?

我能夠為我的問題找到解決方案。 我在下面發布了編輯過的代碼,以便其他人可以從中受益,並將起點(上面的問題)與單元測試的最終代碼進行比較(下面,為了便於說明,將其拆分)。

測試組件模板

import * as angular from 'angular';
import 'angular-mocks/angular-mocks'; 

import weatherModule from '../../../src/app/app.module';
import HeaderComponent, { HeaderComponentController } from '../../../src/header/header.component';

import WeatherSearchService from '../../../src/search/weather-search.service';
import WeatherMapper from '../../../src/common/mapping/weatherMapper.service';

describe('Header Component', () => {
  let $rootScope: ng.IRootScopeService;
  let compiledElement: any;

  beforeEach(angular.mock.module(weatherModule));
  beforeEach(angular.mock.module('templates'));

  beforeEach(angular.mock.inject(($compile: ng.ICompileService,
                                 _$rootScope_: ng.IRootScopeService) => {
    $rootScope = _$rootScope_.$new();
    let element = angular.element('<header-weather></header-weather>');
    compiledElement = $compile(element)($rootScope)[0];
    $rootScope.$digest();
}));

至於指令,我們還需要編譯相關模板並觸發摘要循環。


完成此步驟后,我們可以測試生成的模板代碼:

describe('WHEN the template is compiled', () => {
    it('THEN the info label text should be displayed.', () => {
        expect(compiledElement).toBeDefined();
        let expectedLabelText = 'Here the text you want to test';

        let targetLabel = angular.element(compiledElement.querySelector('.label-test'));
        expect(targetLabel).toBeDefined();
        expect(targetLabel.text()).toBe(expectedLabelText);
    });
});


測試組件控制器
我用jasmine.createSpyObj創建了兩個jasmine.createSpyObj對象。 通過這種方式,可以創建控制器的實例,並使用所需的方法傳遞模擬對象。
由於我的情況下的callFake方法是返回一個promise,我們需要使用jasmine.SpyAnd命名空間中的callFake方法並返回一個已解析的promise。

 describe('WHEN searchCity function is called', () => {

    let searchMock: any;
    let mapperMock: any;
    let mockedExternalWeatherData: any; 

    beforeEach(() => {
        searchMock = jasmine.createSpyObj('SearchServiceMock', ['getWeatherForecast']);
        mapperMock = jasmine.createSpyObj('WeatherMapperMock', ['convertSingleWeatherForecastToDto']);
        mockedExternalWeatherData = {}; //Here I pass a mocked POCO entity (removed for sake of clarity)
    });

    it('WITH proper city name THEN the search method should be invoked.', angular.mock.inject((_$q_: any) => {

        //Arrange
        let $q = _$q_;
        let citySearchString = 'Roma';

        searchMock.getWeatherForecast.and.callFake(() => $q.when(mockedExternalWeatherData));                
        mapperMock.convertSingleWeatherForecastToDto.and.callFake(() => $q.when(mockedExternalWeatherData));

        let headerCtrl = new HeaderComponentController(searchMock, $rootScope, mapperMock);

        //Act 
        headerCtrl.searchCity(citySearchString);

        //Assert
        expect(searchMock.getWeatherForecast).toHaveBeenCalledWith(citySearchString);
    }));
  });
});

謝謝你的帖子! 我同時在同一個問題上工作,也找到了解決方案。 但是這個hero示例不需要編譯組件(也不需要摘要),但使用$componentController ,也可以定義綁定。

my-components模塊 - my-components.module.ts:

import {IModule, module, ILogService} from 'angular';
import 'angular-material';

export let myComponents: IModule = module('my-components', ['ngMaterial']);

myComponents.run(function ($log: ILogService) {
  'ngInject';

  $log.debug('[my-components] module');
});

英雄組件 - my-hero.component.ts

import {myComponents} from './my-components.module';
import IController = angular.IController;

export default class MyHeroController implements IController {
  public hero: string;

  constructor() {
    'ngInject';
  }
}

myComponents.component('hero', {
  template: `<span>Hero: {{$ctrl.hero}}</span>`,
  controller: MyHeroController,
  bindings: {
    hero: '='
  }
});

英雄spec文件 - my-hero.component.spec.ts

import MyHeroController from './my-hero.component';
import * as angular from 'angular';
import 'angular-mocks';

describe('Hero', function() {
  let $componentController: any;
  let createController: Function;

  beforeEach(function() {
    angular.mock.module('my-components');

    angular.mock.inject(function(_$componentController_: any) {
      $componentController = _$componentController_;
    });
  });

  it('should expose a hero object', function() {
    let bindings: any = {hero: 'Wolverine'};
    let ctrl: any = $componentController('hero', null, bindings);

    expect(ctrl.hero).toBe('Wolverine');
  })
});

注意:在測試綁定時修復錯誤需要一些時間:

$compileProvider doesn't have method 'preAssignBindingsEnabled'

原因是角度和角度模擬之間的版本差異。 解決方案由: Ng-mock提供:$ compileProvider沒有方法'preAssignBindingsEnabled`

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM