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

                由于在上個小節中啟用了**數據源**機制,當用戶的登錄狀態發生改變時。負責記錄用戶登錄狀態的數據源能夠及時的將登錄狀態的最新值發送給所有的訂閱者。所以注銷功能的實現只需要向該數據源發送false值即可。 # 原型 找到nav組件,并在V層中參考bootstrap的示例文檔加入注銷按如下: src/app/nav/nav.component.html ```html ... <form class="form-inline"> <button class="btn btn-success" type="button" (click)="onLogout()">注銷</button> </form> </nav> ``` 對應在C層中增加onLogout方法: src/app/nav/nav.component.ts ```html onLogout() { console.log('onLogout'); } ``` ## 單元測試 使用單元測試以確認用戶點擊"注銷"按鈕時成功的觸發了C層的onLogout方法: src/app/nav/nav.component.spec.ts ```javascript fit('點擊注銷按鈕', () => { spyOn(component, 'onLogout'); FormTest.clickButton(fixture, 'button'); expect(component.onLogout).toHaveBeenCalled(); }); ``` # 功能開發 在點擊注銷按鈕時向TeacherService中發送**登錄狀態**為false即可: src/app/nav/nav.component.ts ```javascript onLogout() { this.teacherService.setIsLogin(false); } ``` ## 單元測試 當被測組件nav中聲明依賴于TeacherService時,按前面已掌握的測試方法,有兩種解決方面來做測試準備: 第一種方案:由于TeacherService聲明的注入范圍為root,所以當前的測試模塊可以直接獲取到TeacherService。同時又由于TeacherService聲明依賴于HttpClient等服務,所以要使得測試模塊成功的啟用TeacherService,那么直接在測試模塊中注入TeacherService所依賴服務所在的模塊即可,比如TeacherService依賴于HttpClient,則當前測試模塊的準備代碼如下: src/app/nav/nav.component.spec.ts ```javascript imports: [RouterTestingModule, HttpClientTestingModule ? ] ``` 這種方案最大的缺點在于:1. 當前測試的為nav組件,卻需要考慮TeacherService的依賴,這脫離了測試主線,跑題了。在測試nav組件時我們希望僅考慮該組件的依賴,而不希望再去考慮依賴的依賴。 2. 當依賴的服務的依賴鏈較短時還行,在如果nav組件依賴于a,然后a又依賴于b,b又依賴于c,等等等等。這在測試的構建中無疑走向一個無底洞。3. 這極不易于維護。以TeacherService為例。當前項目中Login及nav組件分別依賴于TeacherService,同時TeacherService組件又聲明依賴于HttpClient。那么在構建login及nav的測試模塊時,則需要分別聲明依賴于HttpClient。一旦TeacherService的依賴發生變更,比如TeacherService又聲明依賴于Router,那么此時login及nav的測試模塊便需要分別增加對Router的依賴支持。不止如此,在這種測試模式下,也會給維護人員帶來一定的思想負擔:本來僅是給TeacherService中增加了一個依賴,為何nav及Login組件報錯呢? 第二種方案:為了避免測試模塊在獲取TeacherService時使用真實的TeacherService而帶來的第一點帶來的種種問題。可以在測試模塊中使用provide為TeacherService指定提供者TeacherStubService。這樣一來當測試模塊需要TeacherService時,將直接使用該TeacherStubService。 src/app/nav/nav.component.spec.ts ```javascript providers: [ {provide: TeacherService, useClass: TeacherStubService} ] ``` 這種方式規避了方案一中的弊端。但當組件與服務的依賴關系增加多時會為書寫增加一些負擔。比如Login及nav組件同時聲明依賴于TeacherService及Router,則需要于兩個組件測試模塊中分別為TeacherService及Router聲明提供者TeacherStubService及RouterStub。 本節提供的另一種新的解決方案將完美的規避上述兩種方案的弊端,從而使構建單元測試模塊變成一件非常輕松的事情。 # TestModule 如下圖首先新建一個TeacherService的替身TeacherServiceStub ![](https://img.kancloud.cn/52/be/52be7885992474d681f06fa812e287d5_518x105.png) 接著新建一個TestModule,并在該模塊中使用聲明:使用TeacherStubService來代替TeacherService ![](https://img.kancloud.cn/a0/cf/a0cfb3f5fdbeaae909c15e6440469d0d_539x341.png) 接下來在相應的動態測試模塊中引入該TestModule ![](https://img.kancloud.cn/e9/11/e911d1a273318b26c486ff6c0776515e_603x649.png) 則相應的動態測試模塊將讀取providers中的信息,從而將TeacherService做為依賴TeacherService注入對應的測試模塊: ![](https://img.kancloud.cn/df/6e/df6e82296885af9c3aff2536df1491f3_713x646.png) 同時,還可以在測試模塊聲明其它真實服務的替身。如此一來,可以首先在TestModule中聲明項目中所用服務的替身,然后在對應的測試模塊中聲明`import TestModule`,則無論測試組件依賴于哪些服務都可以被TestModule中的替身所滿足。下圖展示了TestModule是如何同時提供TeacherService及Router替身的。 ![](https://img.kancloud.cn/ff/79/ff79024af3e9247c7120915dc0de63f8_820x685.png) ## 初始化 ``` panjiedeMac-Pro:web-app panjie$ cd src/app/ panjiedeMac-Pro:app panjie$ ng g module test CREATE src/app/test/test.module.ts (190 bytes) ``` ## 創建TeacherStubService 進入test文件夾并創建service文件夾,接著進行service文件夾,使用`ng g s teacher-stub --skip-tests`命令來創建一個沒有單元測試文件的服務。 ``` panjiedeMac-Pro:service panjie$ ng g s teacher-stub --skip-tests ? CREATE src/app/test/service/teacher-stub.service.ts (140 bytes) ``` * ? 加入`--skip-tests`以避免創建冗余的單元測試文件 打開`src/app/test/service/teacher-stub.service.ts`,刪除`@Injectable`的部分以防止誤將其做為非測試的依賴被添加。 ```javascript @Injectable({ ? providedIn: 'root' ? }) ? export class TeacherStubService { constructor() { } } ``` * @Injectable的作用是設置注入范圍,即聲明在某個范圍以內該服務可以被做為依賴自動注入。 * 去除@Injectable,以避免在開發過程中**誤**注入此測試服務。 ## 加入providers ``` @NgModule({ declarations: [], imports: [ CommonModule ], providers: [ {provide: TeacherService?, useClass: TeacherStubService?} ] }) export class TestModule { } ``` * ?? 使用TeacherStubService來代替TeacherService ## 測試 src/app/nav/nav.component.spec.ts ```javascript beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [NavComponent], imports: [RouterTestingModule, TestModule ? ], }) .compileComponents(); })); fit('測試依賴注入', () => { const service = TestBed.get(TeacherService); console.log(service); }); ``` 查看控制臺打印信息如下: ![](https://img.kancloud.cn/d5/a4/d5a4ad5908de8b07805787e361a6cfe7_426x199.png) 控制臺的打印信息印證了TestModule中聲明的provide對于此測試模塊是生效的,當獲取測試模塊中被注入的TeacherService時,實際上獲取到的是TeacherStubService 基于此測試方案,繼續完成nav組件中logout方法的單元測試 # logout單元測試 src/app/nav/nav.component.spec.ts ```javascript fit('onLogout', () => { const service = TestBed.get(TeacherService) as? TeacherService; spyOn(service, 'setIsLogin'); component.onLogout(); expect(service.setIsLogin).toHaveBeenCalledWith(false); }); ``` * ? 使用as進行類型轉換,以便在進一步操作service變量時能夠能到編輯器的進一步提示 ![](https://img.kancloud.cn/10/f7/10f71bc95f51f9698bebb5ecc22111c2_396x109.png) 提示說setisLogin方法并不存在。這個原因是由于使用了TeacherStubService替換了真實的TeacherService,但TeacherStubService尚未添加setIsLogin方法。解決方案如下: src/app/test/service/teacher-stub.service.ts ```javascript export class TeacherStubService { setIsLogin(isLogin: boolean): void { return; } } ``` 再次運行單元測試通過。 # 修正遺漏問題 上個小節中進行了功能修正后并未及時的修正單元測試,當所有的`fit`恢復為`it`后做全局單元測試: ![](https://img.kancloud.cn/1d/84/1d84fee6e3fe24e255cd16a638915e04_618x304.png) ## LoginComponent 此單元測試的具體錯誤信息為: ``` Error: Expected spy log to have been called with [ true ] but it was never called. at <Jasmine> at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/login/login.component.spec.ts:70:25) ``` 說在login.component.spec.ts的第70行的斷言發生錯誤,斷言spy log被調用,但是其根本沒有被調用過。 ```javascript expect(console.log).toHaveBeenCalledWith(true); ``` 查看對應C層的onSubmit方法后,確認該方法的邏輯已經由在控制臺打印日志改為:返回的結果為真,則繼續調用teacherService.setIsLogin方法;為假則在控制臺打印"用戶名密碼錯誤" 修正如下: src/app/login/login.component.spec.ts ```javascript beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LoginComponent], imports: [ ReactiveFormsModule, HttpClientTestingModule ? TestModule ? ] }) .compileComponents(); })); fit('onSubmit', () => { // 獲取teacherService實例,并為其login方法設置替身 const teacherService = TestBed.get(TeacherService) as TeacherService; spyOn(teacherService, 'login').and.returnValue(of(true)); spyOn(teacherService, 'setIsLogin'); spyOn(console, 'log'); // 添加測試數據并調用 component.formGroup.get('username').setValue('testUsername'); component.formGroup.get('password').setValue('testPassword'); component.onSubmit(); // 斷言成功調用teacherService的login方法 expect(teacherService.login).toHaveBeenCalledWith('testUsername', 'testPassword'); expect(teacherService.setIsLogin).toHaveBeenCalledWith(true); }); ``` 測試完返回真以后,繼續測試返回假: src/app/login/login.component.spec.ts ```javascript spyOn(teacherService, 'login').and.returnValue(of(true)); ? spyOn(teacherService, 'login').and.returnValues(of(true), of(false)); ? ... expect(teacherService.setIsLogin).toHaveBeenCalledWith(true); // teacherService.login返回假時 component.onSubmit(); expect(console.log).toHaveBeenCalledWith('用戶名密碼錯誤'); }); ``` * ? 對teacherService.log進行多次調用時應該使用returnValues方法,該方法接收的值將按teacherService.log被調用的順序,每次被調用時返回1個。 對應為TeacherStubService添加login方法 src/app/test/service/teacher-stub.service.ts ```javascript login(username: string, password: string): Observable<boolean> { return null; } ``` 單元測試通過。 ## AppComponent > should create the app 報錯信息如下: ``` 'app-login' is not a known element: 1. If 'app-login' is an Angular component, then verify that it is part of this module. 2. If 'app-login' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("<app-nav *ngIf="isLogin"></app-nav> ``` 這是由于在app組件中使用了login組件,但卻沒有將login組件聲明在declarations中的原因,當前學習過的解決方案為兩個。 第一種解決方案是引入真實的login組件,然后再引入真實的login組件所依賴的其它依賴,比如HttpClientTestingModule。這種依次向上解決依賴的方案的弊端在本節稍前的部分已有所闡述。 第二種方法是在本測試模塊中聲明一個與login組件具有相同selector的測試組件,并將此組件加入到測試模塊中。此方案解決了第一種方案依次向上依賴的問題,在可維護性上也比較出色。但login組件同時被多個組件引用時,則需要在引用其的多個組件的測試模塊中對應加入相同的login組件替身,而這必將違反不造相同的輪子的原則。 在此給出第三方案:在TestModule中聲明與login組件相同selector的同名組件。然后供所有import TestModule的測試模塊使用。 ### 初始化 在進行src/app/test文件夾,新建component文件夾并進入: ``` panjiedeMac-Pro:component panjie$ ng g c login --skip-tests CREATE src/app/test/component/login/login.component.sass (0 bytes) CREATE src/app/test/component/login/login.component.html (20 bytes) CREATE src/app/test/component/login/login.component.ts (266 bytes) UPDATE src/app/test/test.module.ts (478 bytes) ``` ### export 與提供服務替身的思想不同,若想在import TestModule的測試模塊中認識LoginComponent,只需要將其聲明為在exports中即可: src/app/test/test.module.ts ```javascript @NgModule({ declarations: [LoginComponent], imports: [ CommonModule ], exports?: [ LoginComponent ], providers: [ {provide: TeacherService, useClass: TeacherStubService} ] }) export class TestModule { } ``` * ? exports來聲明模塊中的**公有**組件,即該組件可被import TestModule的其它模塊識別被使用 ### 引入TestModule src/app/app.component.spec.ts ```javascript imports: [ RouterTestingModule, TestModule ? ], ``` 加入TestModule后,單元測試通過。此時并不需要像第一種方案一樣去處理復雜的依賴關系,也可以在其它組件引入login組件時引用TestModule從而達到快速的構建測試模塊的目的。 # 集成測試 重啟后臺后數據表可能已被自動清空,此時若想實現登錄功能,則需要你自己想辦法先在數據表中添加一名測試教師。具體測試過程略。 ![](https://img.kancloud.cn/7f/be/7fbe4c7ee34d7b7edcff7892eeb95b58_1425x380.gif) # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.1.5](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.1.5) | - |
                  <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>

                              哎呀哎呀视频在线观看