學習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錯誤。
不管初學什麼技能,必定不要好高騖遠,也不能自做主張,最重要的是遇到問題不要瞎猜,要按照合理的思惟方式來思考。
青銅玩家左思右想半天都沒有解決的問題,對於王者段位來講,就是兩句話的事。這就是青銅和王者的差距。
爲何青銅玩家解決不了呢?由於我一開始的思考方向就錯了。