<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>

                數據編輯前需要獲取路由的中參數id。但由于在非測試環境中:只有將組件應用到具體的模塊中并定義相應的路由規則時,才可能使用url來觸發這個組件并獲取相關的路由值。而測試環境的URL是類似于:[http://localhost:9876/?id=99225629](http://localhost:9876/?id=99225629)這樣的一串值,所以想按傳統的使用URL來觸發此操作就力不從心了。此時,我們就需要借助模塊的配置項`providers`來提供一個模擬路由的`服務`來協助進行模擬測試了。 # providers 我們再次來到項目的根模塊:AppMoudle app.module.ts ``` import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {AppRoutingModule} from './app-routing.module'; import {AppComponent} from './app.component'; import {HttpClientModule} from '@angular/common/http'; import {TeacherAddComponent} from './teacher/teacher-add.component'; import {FormsModule} from '@angular/forms'; import {TeacherEditComponent} from './teacher/teacher-edit.component'; import {TeacherIndexComponent} from './teacher/teacher-index.component'; import {KlassModule} from './klass/klass.module'; @NgModule({ declarations: [ AppComponent, TeacherAddComponent, TeacherEditComponent, TeacherIndexComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule, FormsModule, KlassModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ``` 在前面的章節中,我們已經學習了`declarations`、`imports`以及`bootstrap`的作用,下面我們共同學習下`providers` 的作用。在正式開始學習之前,我們回想一下1.5.1小節中對依賴注入的描述: ![](https://img.kancloud.cn/19/9b/199b63ebdaee93cc83239062e3ca7ecd_814x637.png) 在我們當前的項目,就像這樣: ![](https://img.kancloud.cn/7e/a7/7ea74fe0697cb3dda4f2d65af4f059b7_626x537.png) 如果有了providers那么將是如下情景: ![](https://img.kancloud.cn/a8/ea/a8eafe1de18902a073585b94db73290f_599x533.png) 由上圖我們看到:在班級Module中,我們可以使用`providers`來為其指定一個`大客車`,此時當班級列表組件表示自己需要一個大客車時,便優先使用`providers`中的`大客車`了。 在進行路由模擬的時候,其實也是用的這個原理: ![](https://img.kancloud.cn/2b/08/2b084ca5fd3e4ac0cf8ec01c48b7384a_824x384.png) 假設我們使用新的大客車來替換租車公司的大客車,那么具體使用語法為: ``` providers: [ {provide: 大客車, useClass: 大客車} ] ``` 這樣使用會有一個小問題,由于兩個名字都是大客車,所以angular不知道哪個大客車是哪個大客車。所以在進行這種替換時,我們一般這樣命名(這并不是唯一的解決方法,但卻是最佳實踐): ``` providers: [ {provide: 大客車, useClass: 大客車Stub} ] ``` 此代碼表示當需要`大客車`時,把`大客車Stub`拿過去使用就好了。 ## Coding 按上述的思路,我們新建一個`ActivatedRouteStub`,并在測試模塊中使用providers來設置其替換`ActivatedRoute`。 klass/edit/edit.component.ts ``` import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {EditComponent} from './edit.component'; import {ReactiveFormsModule} from '@angular/forms'; import {RouterTestingModule} from '@angular/router/testing'; import {ActivatedRoute, Router} from '@angular/router'; import {HttpClientTestingModule} from '@angular/common/http/testing'; import {By} from '@angular/platform-browser'; import {DebugElement} from '@angular/core'; describe('klass EditComponent', () => { let component: EditComponent; let fixture: ComponentFixture<EditComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [EditComponent], imports: [ ReactiveFormsModule, HttpClientTestingModule, RouterTestingModule ], providers: [ ? {provide: ActivatedRoute?, useClass: ActivatedRouteStub?} ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(EditComponent); component = fixture.componentInstance; fixture.detectChanges(); }); fit('should create', () => { expect(component).toBeTruthy(); }); }); class ActivatedRouteStub { ? } ``` * ? 自定義一個ActivatedRouteStub(推薦如此命名,但不必須) * ? 使用providers自定義供應商 * 當組件需要?時,new一個?出來提供給該組件 ### 測試 使用`ng test`測試得到如下錯誤: ![](https://img.kancloud.cn/1f/c9/1fc92e5195e005e2852b115111907ab4_898x134.png) 它說在klass/edit/edit.component.ts中的42行發生了:不能夠在undefined上面讀取`subscribe`屬性。 ``` this.route.params.subscribe((param: { id: number }) => { ``` 也就是說this.route.params為undefined。 產生此錯語的原因是這樣:我們在測試模塊中使用了自定義的ActivatedRouteStub來替換原ActivatedRoute。也就是說第42行代碼中的this.route對象的為我們的ActivatedRouteStub,但我們當前的ActivatedRouteStub中沒有任何屬性,所以在獲取params當然為undefined。我們的思想在回到依賴注入上,既然我們指定的特定的對象來做為本模塊的協作者,那么此協作者原則上就必須提供與原對象一模一樣的功能才不會發生調用錯誤。否則某組件聲明需要一輛汽車,但我們提供的汽車沒有行駛的功能,那么組件在使用該汽車時當然就會出錯了。不過雖然組件聲明需要一輛汽車,但其未并使用該汽車的所有功能,比如汽車除了有行駛功能以外,還有救援的功能。那么我們在構建一個專門用來測試的汽車時,只需要把行駛功能模擬出來就可以滿足該組件的功能需求了,而救援功能即使不模擬,也不會影響組件的正常運行。 ## 剝離ActivatedRouteStub 為了更加清晰的來描述該在測試中模擬路由的類,我們刪除klass/edit/edit.component.spec.ts中的ActivatedRouteStub并在同級目錄新建activated-route-stub.ts klass/edit/activated-route-stub.ts ``` import {ReplaySubject} from 'rxjs'; import {ParamMap} from '@angular/router'; export class ActivatedRouteStub { } ``` 然后在klass/edit/edit.component.spec.ts中來引用它: ``` import {ActivatedRouteStub} from './activated-route-stub'; ``` ## 模擬屬性及功能 以模擬ActivatedRoute為例,我們在ActivatedRouteStub中模擬其屬性與功能有兩種方法:第一種是觀察組件調用的代碼,觀察其調用了ActivatedRoute的什么屬性或是功能,然后在ActivatedRouteStub中添加對應的屬性或功能;第二種是借助于單元測試,看單元測試報什么錯誤,進而在ActivatedRouteStub添加對應的屬性和功能。在此,我們使用第二種借助于單元測試的方法: 單元測試報錯誤:TypeError: Cannot read property 'subscribe' of undefined。則我們如下修正: klass/edit/activated-route-stub.ts ``` import {Subject} from 'rxjs'; import {Params} from '@angular/router'; export class ActivatedRouteStub { subject = new Subject<Params?>(); ? readonly params = this.subject.asObservable();? } ``` * ? 聲明一個Subject,該Subject可以訂閱(觀察)別人,也可以做為可被觀察者被別人訂閱(觀察)。 * ? 該Subject發送的數據類型為`Params 鍵值對` * ? 設置屬性`params`的值:經subject轉換得到的可觀察者 **小竅門:**先創建一個Subject,然后使用`asObservable()`轉換為需要的可觀察者 ## 模擬發送數據 klass/edit/edit.component.spec.ts ``` fit('should create', () => { expect(component).toBeTruthy(); let route: ActivatedRouteStub; ? route = TestBed.get(ActivatedRoute); ? route.subject.next({id: 1});? }); ``` * ? 聲明變量類型 * ? 由測試機床中獲取具有ActivatedRoute功能的服務對象,由于我們在providers中重寫了ActivatedRoute,所以最終獲取到的將是我們重寫對的基于ActivatedRouteStub創建的服務對象。 * ? 調用ActivatedRouteStub中的subject的next方法,向組件發送數據 我們在組件的對應位置上打印下獲的值 ,看是否發送成功了 klass/edit/edit.component.spec.ts ``` ngOnInit() { this.formGroup = new FormGroup({ name: new FormControl(), teacherId: new FormControl() }); this.route.params.subscribe((param: { id: number }) => { console.log(param); ? this.setUrlById(param.id); this.loadData(); }); } ``` 控制臺結果: ``` LOG: Object{id: 1} ① Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 0 of 11 SUCCESS (0 secs / 0 secs) Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 1 of 11 (skipped 10) SUCCESS (0.122 secs / 0.103 secs) TOTAL: 1 SUCCESS TOTAL: 1 SUCCESS ``` ## 寫斷言 當路由發生變更時,我們最終期待該組件發起http請求,并在請求完成后,在V層中顯示對應的數據,由此我們的測試代碼如下: klass/edit/edit.component.spec.ts ``` /** * 組件初始化 * 發送路由參數 * 斷言發起了HTTP請求 * 斷言請求的方法為PUT */ fit('should create', () => { expect(component).toBeTruthy(); let route: ActivatedRouteStub; route = TestBed.get(ActivatedRoute); route.subject.next({id: 1}); /*斷言http請求*/ ? const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Klass/1'); expect(req.request.method).toEqual('GET'); req.flush(new Klass(1, '測試編輯班級', new Teacher(1, null, null))); fixture.whenStable().then(() => { // todo: 獲取input的值,被與預期值做比較 }); }); ``` * ? 一個優秀的項目,離不開良好的注釋;一個優秀的項目,離不開良好的注釋規范。該注釋是一個良好的注釋,但卻違背了良好的注釋習慣。 因為在良好的注釋習慣中有一個原則是:盡可能的規避在方法中添加注釋。所以當前我們面臨了一個兩難的問題。如果我們不添加此行注釋,我們則是在書寫不負責的代碼;如果我們添加注釋,就違背了注釋的原則。當遇到此問題時,我們使用剝離新方法來解決。 ### 剝離新方法 klass/edit/edit.component.spec.ts ``` /** * 組件初始化 * 發送路由參數 * 斷言發起了HTTP請求 * 斷言請求的方法為PUT */ fit('should create', () => { expect(component).toBeTruthy(); let route: ActivatedRouteStub; route = TestBed.get(ActivatedRoute); route.subject.next({id: 1}); testGetHttp(1); }); /** * 測試組件發起的GET請求 * @param id 請求的班級ID */ const testGetHttp = (id: number) => { const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne(`http://localhost:8080/Klass/${id}`); expect(req.request.method).toEqual('GET'); req.flush(new Klass(id, '測試編輯班級', new Teacher(1, null, null))); fixture.whenStable().then(() => { // todo: 獲取input的值,被與預期值做比較 }); }; ``` 此時,我們就可以名正言順的為新方法寫注釋了。這樣便即達到了有良好的注釋,又符合我們的良好的注釋習慣。 ### 不造重復的輪子 在剛剛的代碼中我們增加了`todo`,這是因為我們實在不想再寫一些重復的獲取input的值的語句了,我們的的確確已經寫過夠多的這樣的語句了。何苦不在這此稍微的費點力氣,把一些我們常的方法給剝離出來以達到不造重復的輪子且有效的提升生產力的目的呢?為此,我們在app目錄中新建testing文件夾,并在此文件夾中書寫一些在測試中可能被多個測試類使用的方法,比如我們新建一個FormTest類,并在該類中增加一個獲取input值的方法: testing/FormTest.ts ``` import {DebugElement} from '@angular/core'; import {ComponentFixture} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {isNull} from 'util'; /** * 表單測試 */ export class FormTest { /** * 獲取input輸入框的值 * 首先獲取整個V層元素 * 然后根據CSS選擇器,獲取指定的元素 * 最后將獲取的元素轉換為HTMLInput元素并返回該元素的值 * @param fixture 組件夾具 * @param cssSelector CSS選擇器 */ static① getInputValueByFixtureAndCss(fixture: ComponentFixture<any>?, cssSelector: string): string { const debugElement: DebugElement = fixture.debugElement; const nameElement = debugElement.query(By.css(cssSelector)); if (isNull(nameElement)) { return null; } const nameInput: HTMLInputElement = nameElement.nativeElement; return nameInput.value; } } ``` * ① 聲明為靜態方法以表現屬于`類`而非`對象`。 * ? 暫時不要管這個變量類型是怎么來的。 當前目錄樹結構大體如下: ``` panjiedeMac-Pro:app panjie$ tree -L 2 . ├── app-routing.module.ts ├── app.component.html ├── app.component.sass ├── app.component.spec.ts ├── app.component.ts ├── app.module.ts ├── klass │?? ├── add │?? ├── edit │?? ├── index │?? └── klass.module.ts ├── norm │?? └── entity ├── teacher │?? ├── teacher-add.component.html │?? ├── teacher-add.component.ts │?? ├── teacher-edit.component.html │?? ├── teacher-edit.component.ts │?? ├── teacher-index.component.html │?? └── teacher-index.component.ts └── testing └── FormTest.ts ``` 接下來我們回來班級班級組件測試中引用剛剛寫的方法來獲取input的值。 ``` import {FormTest} from '../../testing/FormTest'; /** * 測試組件發起的GET請求 * 斷言請求地址及方法 * 返回數據后,斷言input項成功綁定返回數據 * @param id 請求的班級ID */ const testGetHttp = (id: number) => { const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne(`http://localhost:8080/Klass/${id}`); expect(req.request.method).toEqual('GET'); req.flush(new Klass(id, '測試編輯班級', new Teacher(1, null, null))); fixture.whenStable().then(() => { expect(FormTest.getInputValueByFixtureAndCss(fixture, '#name')).toEqual('測試編輯班級'); expect(FormTest.getInputValueByFixtureAndCss(fixture, '#teacherId')).toEqual('1'); }); }; ``` 測試結果: ``` LOG: Object{id: 1} Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 0 of 11 SUCCESS (0 secs / 0 secs) Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 1 of 11 (skipped 10) SUCCESS (0.071 secs / 0.051 secs) TOTAL: 1 SUCCESS TOTAL: 1 SUCCESS ``` # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.4.3](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.4.3) | - | | 依賴提供商 | [https://www.angular.cn/guide/dependency-injection-providers](https://www.angular.cn/guide/dependency-injection-providers) | 15 | | ActivatedRouteStub | [https://www.angular.cn/guide/testing#activatedroutestub](https://www.angular.cn/guide/testing#activatedroutestub) | 10 | | Subject = Observable + Observer | [https://wiki.jikexueyuan.com/project/rxjava//chapter2/subject\_observable\_observer.html](https://wiki.jikexueyuan.com/project/rxjava//chapter2/subject_observable_observer.html) | 10 | | 依賴注入 | 參閱教程1.5.1 | - | | 觀察者模式 | 參閱教程2.4.7 | - | | Angular 中的觀察者 | [https://www.angular.cn/guide/observables-in-angular#observables-in-angular](https://www.angular.cn/guide/observables-in-angular#observables-in-angular) | - | | 可觀察對象 | [https://www.angular.cn/guide/observables](https://www.angular.cn/guide/observables) | - |
                  <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>

                              哎呀哎呀视频在线观看