<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                本節可能稍微有些難度,目的在于帶領大家更多的了解Angular。如果在學習第二遍后仍然心中沒有頭緒的話,建議學習完整個教程或對Angular有了更深入的認識或是以后在生產中碰到類似的需求時再閱讀。 當前項目我們執行`ng t`的時候,由于測試文件執行的過程是隨機的。在某些時候,將發生類似于如下錯誤: ![image-20210415104727722](https://img.kancloud.cn/c1/43/c1432b2cbbe532bedda51852726456a8_2988x186.png) 或者在控制臺中報如下錯誤: ![image-20210415104748537](https://img.kancloud.cn/5e/1d/5e1d021743e3cf8c4afc5a84bc89c12e_2574x140.png) 以及以下錯誤: ![image-20210415104806534](https://img.kancloud.cn/ac/b8/acb865d4ae4e6be833213e68dd5558b6_2566x124.png) 或以下錯誤: ![image-20210415104819641](https://img.kancloud.cn/82/de/82deda7b99e07157f069251cd0808971_2582x138.png) 或以下錯誤: ![image-20210415104839087](https://img.kancloud.cn/7d/7b/7d7b429fb1497f74a53606f483a195fd_2552x138.png) 或者其它的類似錯誤。 但奇怪的是,無論我們單獨執行任意一個單元測試文件,都不會發生任何異常。這又是為何呢? ## 單例模式 在繼續學習之前,讓我們新建個測試文件復習一下前面講過的單例模式。在`src/app`目錄下新建`single-case.spec.ts`文件,加入一些前面我們在單例模式的學習中已經認知的代碼,其代碼初始化如下: ```typescript import {TestBed} from '@angular/core/testing'; describe('單例模式相關測試', () => { beforeEach(async () => { await TestBed.configureTestingModule({}) .compileComponents(); }); }); ``` 然后新建一個服務A: ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -1,6 +1,18 @@ import {TestBed} from '@angular/core/testing'; +import {Injectable} from '@angular/core'; +import {randomNumber} from '@yunzhi/ng-mock-api'; describe('單例模式相關測試', () => { + @Injectable({providedIn: 'root'}) + class A { + key: number; + + constructor() { + console.log('a constructor be called'); + this.key = randomNumber(); + } + } + beforeEach(async () => { await TestBed.configureTestingModule({}) .compileComponents(); ``` 再建立兩個依賴于A的服務BC: ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -13,6 +13,18 @@ describe('單例模式相關測試', () => { } } + @Injectable({providedIn: 'root'}) + class B { + constructor(private a: A) { + } + } + + @Injectable({providedIn: 'root'}) + class C { + constructor(private a: A) { + } + } + beforeEach(async () => { await TestBed.configureTestingModule({}) .compileComponents(); ``` 然后新建一個測試用例,在該測試用例中,分別獲取Angular托管下B以及C的實例。 ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -29,4 +29,11 @@ describe('單例模式相關測試', () => { await TestBed.configureTestingModule({}) .compileComponents(); }); + + fit('驗證單例', () => { + const b = TestBed.inject(B); + console.log(b); + const c = TestBed.inject(C); + console.log(c); + }); }); ``` 控制臺如下: ![image-20210415120922802](https://img.kancloud.cn/12/84/1284e5fbfa54d44428f89e6234ab2db0_1398x322.png) 如我們的預期一致,在Angular中服務A是個單例的,且在整個生命周期中僅被初始化一次。 我們說之所以能夠在BC中獲取到相同的A,是同于A使用了`@Injectable({providedIn: 'root'})`注解。該注解的作用是在根模塊中提供A,所以在Angular項目的任意模塊需要A的實例時,都會先查是否有了A的實例,如果有則直接注入,如果沒有則先實例化一個再注入。 ## providers 預使模塊能提供A的能力,除了可以把A注入到根模塊外,還可以將其聲明在`providers`中。為此,我們先刪除A類上的`@Injectable({providedIn: 'root'})`注解: ```typescript +++ b/first-app/src/app/use-class.spec.ts @@ -3,7 +3,6 @@ import {Injectable} from '@angular/core'; import {randomNumber} from '@yunzhi/ng-mock-api'; describe('useClass相關測試', () => { - @Injectable({providedIn: 'root'}) class A { key: number; ``` 此時將出現找不到A的提供者的錯誤: ![image-20210415135249554](https://img.kancloud.cn/2a/be/2abe99c597e4c711bbd09ecf0c1cb024_1666x168.png) 接下來將A聲明在當前動態測試模塊的`providers`中: ```typescript +++ b/first-app/src/app/use-class.spec.ts @@ -25,7 +25,11 @@ describe('useClass相關測試', () => { } beforeEach(async () => { - await TestBed.configureTestingModule({}) + await TestBed.configureTestingModule({ + providers: [ + ??{provide: ①A, useClass: ②A} + ] + }) .compileComponents(); }); ``` ?? 如此當前模塊便擁有了投供A的能力。該行語句可理解為:當需要①A時,使用②A是否有可用的對象,有則直接使用,沒有則實例化一個。當然,像這種使用A提供A的語句,還可以簡寫為`{provide: A}`或者`A`。也就是說下面①②③三種寫法的是等價的: ```typescript providers: [ A, ① {provide: A}, ② {provide: A, useClass: A} ③ ] ``` ## forRoot() 前面我們學習過:使用`@Injectable({providedIn: 'root'})`時該服務的注入范圍為根模塊。而Angular中所有的模塊都屬于根模塊的子模塊,所以如果一個服務聲明為`@Injectable({providedIn: 'root'})`,則無論在哪個模塊中注入它,最終都會得到相同的實例。 但有一些服務即需要將其注入范圍聲明為根模塊,又不能夠使用`@Injectable({providedIn: 'root'})`注解,比如說需要進行配置的路由模塊,或是需要進行配置的`MockApi`模塊,這時候便需要Angular建議的`forRoot()`方法來解決這個問題。 比如我們在提供A時,手動的設定key的值: ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -6,9 +6,9 @@ describe('單例模式相關測試', () => { class A { key: number; - constructor() { + constructor(key: number) { console.log('a constructor be called'); - this.key = randomNumber(); + this.key = key; } } ``` 此時歷史的寫法則將報一個無沒初始化A的錯誤: ![image-20210415150836227](https://img.kancloud.cn/08/79/087935e4d289669aff520223ec0c82fb_1516x108.png) 的確如此:我們在構造函數中聲明了一個參數key,但Angular并不清楚這個key具體應該賦給它什么值。此時可以創建一個擁有靜態的`forRoot()`模塊: ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -1,5 +1,5 @@ import {TestBed} from '@angular/core/testing'; -import {Injectable, ModuleWithProviders} from '@angular/core'; +import {Injectable, ModuleWithProviders, NgModule} from '@angular/core'; describe('單例模式相關測試', () => { @@ -12,6 +12,18 @@ describe('單例模式相關測試', () => { } } + @NgModule() + class AModule { + static ①forRoot(key: number): ②ModuleWithProviders<AModule> { + return { + ngModule: ③AModule, + providers: [ + {provide: A, ④useValue: new ⑤A(key)} + ] + }; + } + } + @Injectable({providedIn: 'root'}) class B { constructor(private a: A) { ``` - ① `forRoot()`方法可以接收任意參數 - ② 返回值類型為`ModuleWithProviders`,在該類型上指名了提供的服務類型AModule - ③ 返回值的ngModule字段對應`ModuleWithProviders`上的泛型 - ④ `useValue`用于返回一個對象。意為當Angular需要一個A實例時,如果有則直接返回;如果沒有,則使用`useValue`后的實例 - ⑤ 提供了一個根據參數設置了`key`的實例 如此以來便可以使用`forRoot()`方法來添加一個配置了參數的A服務了: ```typescript +++ b/first-app/src/app/single-case.spec.ts @@ -38,8 +38,8 @@ describe('單例模式相關測試', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - providers: [ - {provide: A, useClass: A.forRoot(123)} + imports: [ + AModule.forRoot(123) ] }) .compileComponents(); ``` 注意,此時將`AModule.forRoot()`方法聲明在`imports`中而不是`providers`中。如此以來,便可以在動態模塊中`imports`相應模塊的同時,加入特定的配置參數`123`了: ![image-20210415155002300](https://img.kancloud.cn/66/5f/665f9025920d8a3180da5c113fd112df_810x194.png) ## 步入正題 鋪墊了這么多,終于可以步入正題了。之所以在單元測試中出現了隨機出現了那么多的錯誤,是由于`TestBed`在處理攔截器時使用了惰性加載。 我們在單元測試中存在以下代碼①: ```typescript src/app/clazz/class-select.component.spec.ts providers: [ { provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiTestingInterceptor.forRoot([ TeacherMockApi ]) } ``` 以下代碼②: ```typescript src/app/clazz/add/add.component.mock-api.sepc.ts providers: [ { provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiInterceptor.forRoot([ClazzMockApi, TeacherMockApi]) } ``` 以及以下代碼③: ```typescript src/app/mock-api/mock-api-testing.module.ts providers: [ { provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiTestingInterceptor.forRoot([ ClazzMockApi, TeacherMockApi, StudentMockApi ]) } ] ``` Angular的惰性加載機制使得其在進行多文件的單元測試時,多個文件中的TestBed共享了`MockApiTestingInterceptor.forRoot()`方法中的返回值,該返回值是使用不同的MockApi進行配置的。 所以如果其共享的是①的返回值,該返回值則僅僅添加了`TeacherMockApi`對應的幾個模擬接口,此時依賴于另外兩個模擬接口的單元測試將報錯;如果其共享的是②的返回值,該返回值則僅僅添加了`ClazzMockApi, TeacherMockApi`兩個模擬接口, 此時依賴于另一個模擬接口的單元測試將報錯;如果其共享的是③的返回值,則將添加所有的模擬接口,此時所有的單元測試將正常執行。 問題猜測到了,解決問題的方案也就不難了。只需要在所有應用后臺模擬API的地方添加上所有的Mock文件即可。或是刪除在其它測試文件中加入Http攔截器的代碼,改為引用`MockApiTestingModule`從而達到引入所有API的目的。 具體修正文件列表如下: ```typescript clazz/add/add.component.mock-api.spec.ts clazz/clazz.component.spec.ts clazz/klass-select/klass-select-form-control.component.spec.ts clazz/klass-select/klass-select.component.spec.ts ``` 請自行修正。 <hr> 模擬接口的錯誤修正后,最后再修正個邏輯上的錯誤。把鼠標放到出錯的文件名上,瀏覽器將顯示該文件所在的具體位置。 ![image-20210415161608176](https://img.kancloud.cn/95/ee/95ee97f9fcd84e096abbd14d6308ff7e_2092x414.png) 找到該文件的第29行代碼: ```typescript beforeEach(() => { fixture = TestBed.createComponent(EditComponent); component = fixture.componentInstance; ?? fixture.detectChanges(); }); ``` 說明在組件初始化時發生了錯誤。繼續查看錯誤的堆棧信息發現是在組件的46行,調用loadbyId方法時觸發了MockApi,近而發生了異常: ![image-20210415161848961](https://img.kancloud.cn/ea/70/ea706a6291baa412fb40cda71486a728_1012x272.png) ```typescript ngOnInit(): void { 34 const id = this.activatedRoute.snapshot.params.id; 35 ?? this.loadById(+id); } /** * 由后臺加載預編輯的班級. * @param id 班級id. */ loadById(id: number): void { console.log('loadById'); this.formGroup.get('id')?.setValue(id); this.httpClient.get<Clazz>('/clazz/' + id.toString()) 46 ?? .subscribe(clazz => { console.log('接收到了clazz', clazz); this.nameFormControl.patchValue(clazz.name); this.formGroup.get('teacherId')?.setValue(clazz.teacher.id); }, error => console.log(error)); } ``` 分析代碼可知,這是由于在組件初始化時,將34行代碼獲取到的ID值null再轉換為數字時發生了NaN錯誤造成的。預解決這個錯誤,則需要保證37行代碼接收的值非null。 為此,我們組件初始化以前為這個34的代碼設置一個id值: ```typescript +++ b/first-app/src/app/clazz/edit/edit.component.spec.ts @@ -6,6 +6,7 @@ import {MockApiTestingModule} from '../../mock-api/mock-api-testing.module'; import {ReactiveFormsModule} from '@angular/forms'; import {getTestScheduler} from 'jasmine-marbles'; import {RouterTestingModule} from '@angular/router/testing'; +import {ActivatedRoute} from '@angular/router'; describe('EditComponent', () => { let component: EditComponent; @@ -26,6 +27,8 @@ describe('EditComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(EditComponent); component = fixture.componentInstance; + const activatedRoute = TestBed.inject(ActivatedRoute); + activatedRoute.snapshot.params.id = 123; ?? fixture.detectChanges(); }); ``` 這樣的話,當執行到??組件初始化的代碼時,獲取`activatedRoute.snapshot.params.id`便會得到數據123,而非null了。 至此,我們解決了單元測試中所有非預期錯誤。 | 名稱 | 鏈接 | | -------- | ------------------------------------------------------------ | | 單例服務 | [https://angular.cn/guide/singleton-services](https://angular.cn/guide/singleton-services) | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step7.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.3.zip) |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看