学习Angular的时候,总感受特别的复杂、高级,以致于产生畏惧心理,这种心理尤为体如今单元测试上。webpack
今天被醍醐灌顶以后,感受单元测试的报错信息也不是那么难看懂了。web
(文章的后半段是记录我本身的一次单元测试的过程,为了避免耽误读者时间,我把结论写在第一小节。)缓存
启动单元测试后,映入眼帘的是一堆信息:app
先来看顶部的信息:async
最上面的一排点···············是整个项目的测试总数,下面的Ran 1 of 58 specs是一共58个测试,本次测试启动了一个。ide
下面的1 spec, 1 failure是本次启动的一个测试中,有一个出错了,而后列出了全部报错的测试项,以及错误信息。单元测试
而后看错误信息:学习
顶上的 TypeError: Cannot set property 'workId' of undefined 是主要的错误类型。测试
本文举例的错误是类型错误,不能对一个 undefined 对象调用 workId 属性。this
下面的那一大片代码,是方法的堆栈。
举个例子:若是 A( )调用了 B( ), B( ) 调用了C( ),那么堆栈状况就是这样:
咱们知道,栈是先进后出,队列是先进先出,这里使用的就是栈。
因此,这张图的意思,就是堆栈,最上面的方法,是当前的活跃方法,也就是出错的方法:
能够清晰的看到at WorkStubService.getById (http://localhost:9877/_karma_webpack_/src/app/service/service-tesing/work-stub.service.ts:76:32)
翻译一下就是,错误发生在WorkStubService类的getById方法,连接的最后显示了,是work-stub.service.ts文件的第76行的第32个字符,出现了问题。
而后定位到这行代码,就能够清楚的找到问题了。
分析报错信息的方法介绍,到此结束。
计划测试EditComponent组件的C层初始化。
C层:
export class EditComponent implements OnInit { work = new Work(); params = { workId: 0 }; constructor(private workService: WorkService) { } ngOnInit() { this.params.workId = 0; this.load(); } public load() { this.workService.getById({id: this.params.workId}) .subscribe((data) => { this.work = data; console.log(this.work); }, () => { console.log('error'); }); } }
ngOninit调用load方法,load调用service来获取数据,
因此测试的思路就是让组件初始化,而后断言C层向M层的传值,以及断言M层的返回值便可。
describe('Page -> Teacher -> EditComponent', () => { let component: EditComponent; let fixture: ComponentFixture<EditComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ EditComponent ], imports: [ HttpClientTestingModule, ], providers: [ {provide: WorkService, useClass: WorkStubService} ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(EditComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('组件初始化发起请求测试', () => { /* 获取请求参数 */ const workService: WorkStubService = TestBed.get(WorkService); const queryParam = workService.pageParamsCache; /* 断言传入的参数值与组件中的参数值相同 */ expect(queryParam.workId).toEqual(component.params.workId); }); it('组件初始化V层渲染', () => { /* 断言总行数及第一行的内容绑定符合预期 */ expect(component.work.id).toBe(1); expect(component.work.content).toBe('<p>content</p>'); expect(component.work.item).toBe(new Item({name: 'Item'})); expect(component.work.score).toBe(100); expect(component.work.student).toBe(new Student({name: 'Student'})); expect(component.work.reviewed).toBe(true); }); });
一共三个小的测试项。为了模拟返回值,还须要一个假的M层。
export class WorkStubService { constructor() { } /* 传入参数缓存 */ pageParamsCache: { page: number, size: number ,workId: number}; /** * getById模拟方法 * @param params 查询参数 */ getById(params: { id: number }): Observable<Work> { this.pageParamsCache.workId = params.id; const mockResult = new Work( { id: 1, content: '<p>content</p>', item: new Item({name: 'Item'}), score: 100, student: new Student({name: 'Student'}), reviewed: true}); return of(mockResult); } }
而后一运行测试,我懵了,全部模拟的返回值,全都是undefined:
带着疑惑的心态,开始看本身的代码,感受哪里都没问题...
更况且这些代码是从老项目里面粘过来的,“不可能”出错。
为了找出不一样,我把代码和老项目一行一行的对照,的确没有问题。
而后我发现了模拟M层的getById方法是灰色,还执拗的认为多是没有被调用:
我又打了一堆断点,事实证实,即便是灰色,也确实被执行了。
直到这时,我尚未仔细的看报错信息。
终于,没法本身解决了,只能问老师,而后才发现,关键在于报了一个TypeError: Cannot set property 'workId' of undefined
一看,undefined,因而又傻傻的去C层找定义变量的代码,
都有初始值,怎么会是undefined呢??
后来老师又提示了,看报错信息。才知道那一大堆信息,其实是堆栈,若是要找到具体的出错位置,只须要看最上面的一行,就能够了。
at WorkStubService.getById (http://localhost:9876/_karma_webpack_/src/app/service/service-tesing/work-stub.service.ts:76:32)
定位到代码,是模拟服务层的这一行出现了问题:
这个变量是这样定义的,换言之,只定义了类型,并无初始化:
因此,在undefined对象上调用wordId,固然会出错了。我恍然大悟。
而后另外一个疑惑产生了,个人代码是粘过来的,为何在以前的项目里没报错呢?
仔细的看了老项目,才发现个人错误源自于一个细微的改动。
老项目是这么写的:
this.pageParamsCache = params;
把全部的参数直接赋值给pageParamsCache,不存在对它调用属性的问题。
而我为了方便,改为了:
this.pageParamsCache.workId = params.id;
这一改,就把本来的总体赋值,变成了赋值单个属性,可是pageParamsCache并无初始化,因此才出现了undefined错误。
不管初学什么技能,必定不要好高骛远,也不能自做主张,最重要的是遇到问题不要瞎猜,要按照合理的思惟方式来思考。
青铜玩家左思右想半天都没有解决的问题,对于王者段位来讲,就是两句话的事。这就是青铜和王者的差距。
为何青铜玩家解决不了呢?由于我一开始的思考方向就错了。