Angularjs 带茉莉花的1.x角组件单元测试(使用打字脚本、网页包)
我正在使用angular 1.6、打字脚本、网页包、karma和jasmine编写一个应用程序。我能够为angular services创建单元测试,但现在我在测试组件方面遇到了麻烦。在网上,我找到了不同的例子(),但没有一个明确的指南来解释如何使用上述技术集测试angular 1组件 我的组件(HeaderComponent.ts):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
从“../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'