使用Jasmine单元测试角度1.x组件(使用Typescript,Webpack)

Fra*_*sco 2 angularjs typescript karma-jasmine webpack angular-components

我正在使用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)));
 }
}
Run Code Online (Sandbox Code Playgroud)

单元测试:

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();
});
Run Code Online (Sandbox Code Playgroud)

对我来说不清楚如何访问控制器类,以便测试组件业务逻辑.我尝试注入$ componentController,但我不断收到错误"未捕获的TypeError:无法设置未定义的属性'mock',我认为这与未正确注入的角度模拟有关.

任何人都可以建议解决方案的方法或网站在哪里可以找到有关使用打字稿和webpack进行单元测试角度1组件的更多详细信息?

Fra*_*sco 6

我能够为我的问题找到解决方案.我在下面发布了编辑过的代码,以便其他人可以从中受益,并将起点(上面的问题)与单元测试的最终代码进行比较(下面,为了便于说明,将其拆分).

测试组件模板:

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();
}));
Run Code Online (Sandbox Code Playgroud)

至于指令,我们还需要编译相关模板并触发摘要循环.


完成此步骤后,我们可以测试生成的模板代码:

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);
    });
});
Run Code Online (Sandbox Code Playgroud)


测试组件控制器:
我创建了两个模拟对象jasmine.createSpyObj.通过这种方式,可以创建控制器的实例,并使用所需的方法传递模拟对象.
由于我的情况中的模拟方法是返回一个promise,我们需要使用命名空间中的callFake方法jasmine.SpyAnd并返回已解析的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);
    }));
  });
});
Run Code Online (Sandbox Code Playgroud)