零、前言
学习Angular的时候,总感觉特别的复杂、高级,以至于产生畏惧心理,这种心理尤其体现在单元测试上。
今天被醍醐灌顶之后,感觉单元测试的报错信息也不是那么难看懂了。
(文章的后半段是记录我自己的一次单元测试的过程,为了不耽误读者时间,我把结论写在第一小节。)
一、分析报错信息
启动单元测试后,映入眼帘的是一堆信息:
先来看顶部的信息:
最上面的一排点···············是整个项目的测试总数,下面的Ran 1 of 58 specs是一共58个测试,本次测试启动了一个。
下面的1 spec, 1 failure是本次启动的一个测试中,有一个出错了,然后列出了所有报错的测试项,以及错误信息。
然后看错误信息:
顶上的 TypeError: Cannot set property 'workId' of undefined 是主要的错误类型。
本文举例的错误是类型错误,不能对一个 undefined 对象调用 workId 属性。
下面的那一大片代码,是方法的堆栈。
举个例子:如果 A( )调用了 B( ), B( ) 调用了C( ),那么堆栈情况就是这样:
我们知道,栈是先进后出,队列是先进先出,这里使用的就是栈。
- 当A( )方法被调用时,内存加载A( ),此时A就在栈上
- 执行的时候突然发现,A( )需要调用B( )方法,此时应该加载B( )
- 但A( )还没有执行完毕,不能释放,所以就用栈的方式,把A( )压在下面并且冻结
- B( )的执行过程中又需要C( ),所以B冻结,C入栈。
- 等到C( )执行完之后,把结果返回给B( ),此时C( )被释放,出栈
- 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;
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('content
');
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 {
this.pageParamsCache.workId = params.id;
const mockResult = new Work(
{
id: 1, content: 'content
', 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错误。
总结
无论初学什么技能,一定不要好高骛远,也不能自作主张,最重要的是遇到问题不要瞎猜,要按照合理的思维方式来思考。
青铜玩家冥思苦想半天都没有解决的问题,对于王者段位来说,就是两句话的事。这就是青铜和王者的差距。
为什么青铜玩家解决不了呢?因为我一开始的思考方向就错了。
祥哥NB!
NB就完事了!