Angularjs 带茉莉花的1.x角组件单元测试(使用打字脚本、网页包)

Angularjs 带茉莉花的1.x角组件单元测试(使用打字脚本、网页包),angularjs,typescript,webpack,karma-jasmine,angular-components,Angularjs,Typescript,Webpack,Karma Jasmine,Angular Components,我正在使用angular 1.6、打字脚本、网页包、karma和jasmine编写一个应用程序。我能够为angular services创建单元测试,但现在我在测试组件方面遇到了麻烦。在网上,我找到了不同的例子(),但没有一个明确的指南来解释如何使用上述技术集测试angular 1组件 我的组件(HeaderComponent.ts): 从“../models/weatherforecast”导入{IWeatherforecast}; 从“../search/weather search.ser

我正在使用angular 1.6、打字脚本、网页包、karma和jasmine编写一个应用程序。我能够为angular services创建单元测试,但现在我在测试组件方面遇到了麻烦。在网上,我找到了不同的例子(),但没有一个明确的指南来解释如何使用上述技术集测试angular 1组件

我的组件(HeaderComponent.ts):

从“../models/weatherforecast”导入{IWeatherforecast};
从“../search/weather search.service”导入WeatherSearchService;
从“../common/mapping/weatherMapper.service”导入WeatherMapperService;
导出默认类HeaderComponent实现ng.IComponentOptions{
公共约束:任何;
公共控制人:任何;
publiccontrolleras:string='vm';
公共模板URL:string;
公共转置:布尔值=false;
构造函数(){
此文件的绑定={
};
this.controller=磁头组件控制器;
this.templateUrl='src/header/header.html';
}
}
导出类HeaderComponentController{
公共搜索文本:字符串
私人天气数据:IWeatherforecast;
静态$inject:Array=['weatherSearchService',
“$rootScope”,
“weatherMapperService”];
建造商(私人weatherSearchService:weatherSearchService,
私有$rootScope:ng.IRootScopeService,
专用weatherMapperService:weatherMapperService){
}
public$onInit=()=>{
this.searchText='';
}
公共搜索城市=(搜索名称:字符串):void=>{
this.weatherSearchService.getWeatherForecast(searchName)
.然后((天气数据:ng.ihttpromisecallbackarg)=>{
设mappedData=this.weatherMapperService.convertSingleWeatherForecastToToToTo(weatherData.data);
setItem('currentCityWeather',JSON.stringify(mappedData));
此.$rootScope.$broadcast('weatherDataFetched',mappedData);
})
.catch((错误:any)=>console.error('发生错误:'+JSON.stringify(错误));
}
}
单元测试

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();
});
import*作为“angular”的角度;
导入“角度模拟”;
从“../../src/header/header.component”导入HeaderComponent;
描述('标题组件',()=>{
让$compile:ng.ICompileService;
出租范围:ng.IROOTSOPESERVICE;
let元素:ng.IAugmentedJQuery;
每个之前(角度模拟模块(“天气”);
beforeach(angular.mock.inject(函数($compile:ng.ICompileService,$rootScope:ng.IRootScopeService){
$compile=\$compile;
范围=u$rootScope;
}));
在每个之前(()=>{
元素=$compile(“”)(范围);
范围。$digest();
});
对我来说,不清楚如何访问控制器类,以便测试组件业务逻辑。我尝试注入$componentController,但我不断收到错误“Uncaught TypeError:无法设置未定义的属性'mock',我认为这与角度模拟没有正确注入有关


有人能推荐一种解决方案或一个网站,在那里可以找到关于使用typescript和webpack对angular 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();
}));

测试组件控制器:
我使用
jasmine.createSpyObj
创建了两个模拟对象。通过这种方式,可以创建控制器的实例,并使用所需的方法传递模拟对象。
在我的例子中,mocked方法返回一个承诺,因此我们需要使用
jasmine.SpyAnd
命名空间中的
callFake
方法并返回一个已解析的承诺

 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: 英雄组件-my-hero.component.ts 注意:修复测试绑定时的错误花费了一些时间:

$compileProvider doesn't have method 'preAssignBindingsEnabled'

原因是angular和angular mock之间存在版本差异。解决方案是:

在我的情况下,使用$componentController将不是一个选项。我需要将spy对象(searchMock和mapperMock)注入组件构造函数,以测试是否调用了这些方法。此外,$digest()是必需的,因为我需要测试返回承诺的方法。如果不触发摘要循环,这是不可能的。通过$timeout.flush()解析承诺对我很有用。Angular文档还说:“在测试中,可以使用$timeout.flush()同步刷新延迟函数的队列。”很有趣,谢谢你带出来。我会试试的。
 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);
    }));
  });
});
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');
});
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: '='
  }
});
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'