2017-02-09 37 views
0

我正在爲名爲'DashboardComponent'的組件創建一個測試。測試規範很難創建。 Karma拋出錯誤「由模塊'DynamicTestModule'導入的'意外值'DecoratorFactory'」。需要有關Angular2 TestBed配置的幫助。 DecoratorFactory錯誤

我已嘗試刪除代碼,直到錯誤消失,然後添加代碼,直到它重新出現以識別源代碼。但是,這並沒有提供豐富的結果,因爲在使用多種不同配置時代碼會中斷,並且我無法確定哪個配置會導致錯誤存在,並導致錯誤被拋出。我懷疑在所有非失敗嘗試中都存在錯誤的配置,並且某些代碼行(例如添加了it()調用)會導致錯誤在以前不可見時突然顯示。

我希望有比我更有經驗的人會看到我的錯誤並提供一些建議。其他人在StackOverflow上也遇到了同樣的問題,並且爲他們工作的解決方案似乎不適用於這種情況。

碼圖

爲了使代碼庫更容易消化,我創建了一個UML圖,以顯示我試圖在測試牀配置複製依賴關係。

enter image description here

綠色元件是被測試的元件。包元素被導入到模塊中,並且每個服務都有一個模擬類,我們在它們的位置提供模擬類。我們必須聲明三個組件,因爲DashboardComponent的模板引用了PatientListComponent,而PatientListComponent的模板依賴於PatientListItemComponent。

代碼&文件

我希望不要寫一本小說在這個問題,所以我會添加更多的文件&代碼,否則會在請求。在提出要求之前,我會提供那些與識別問題最相關的文件。

應用程序/儀表板/ dashboard.component.spec.ts

import { DebugElement, NgModule } from '@angular/core'; 
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; 
import { FormsModule }    from '@angular/forms'; 
import { By }      from '@angular/platform-browser'; 
import { Router, RouterModule }  from '@angular/router'; 

import { DashboardComponent }  from './dashboard.component'; 
import { PatientListComponent }  from '../patient-list/patient-list.component'; 
import { PatientListItemComponent } from '../patient-list-item/patient-list-item.component'; 
import { AuthenticationService }  from '../authentication.service'; 
import { PatientSummaryService }  from '../patient-summary/patient-summary.service'; 

import { MockAuthService, MockPatientSummaryService, RouterStub } from './dashboard.mocks'; 

/* 
* DebugElement and By are currently not used. I have left them in the import statements above, 
* because any test suite WILL use them when it is fully developed. 
*/ 
describe("DashboardComponent",()=>{ 
    var component : DashboardComponent     = null; 
    var fixture : ComponentFixture<DashboardComponent> = null; 
    beforeEach( 
     async( 
      ()=>{ 
       TestBed.configureTestingModule(
        { 
        imports: [ NgModule, RouterModule, FormsModule ], 
        declarations: [ DashboardComponent, PatientListComponent, PatientListItemComponent ], 
        providers: [ 
         { provide: AuthenticationService, useClass: MockAuthService }, 
         { provide: PatientSummaryService, useClass: MockPatientSummaryService }, 
         { provide: Router, useClass: RouterStub } 
           ] 
        } 
       ).compileComponents(); 
    })); 
    beforeEach(()=>{ 
     fixture = TestBed.createComponent(DashboardComponent); 
    }); 
    it("has a test",()=>{ expect(1).toBe(1);}); 
    /* 
    describe("filter section behavior",()=>{}); 
    describe("list display behavior", async(()=>{ 
     describe("filtered-list behavior", async(()=>{ 
      //component.filterList = true; 
      fixture.detectChanges(); 
      fixture.whenStable().then((done:any)=>{ 
       debugger; 
      }); 
     })); 
     describe("unfiltered-list behavior",()=>{ 
      //component.filterList = false; 
     }); 
    })); 
    */ 
}); 

應用程序/儀表板/ dashboard.mocks.ts

import { DebugElement, NgModule } from '@angular/core'; 
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; 
import { FormsModule }    from '@angular/forms'; 
import { By }      from '@angular/platform-browser'; 
import { Router, RouterModule }  from '@angular/router'; 

import { DashboardComponent }  from './dashboard.component'; 
import { PatientListComponent }  from '../patient-list/patient-list.component'; 
import { PatientListItemComponent } from '../patient-list-item/patient-list-item.component'; 
import { AuthenticationService }  from '../authentication.service'; 
import { PatientSummaryService }  from '../patient-summary/patient-summary.service'; 

import { MockAuthService, MockPatientSummaryService, RouterStub } from './dashboard.mocks'; 

/* 
* DebugElement and By are currently not used. I have left them in the import statements above, 
* because any test suite WILL use them when it is fully developed. 
*/ 
describe("DashboardComponent",()=>{ 
    var component : DashboardComponent     = null; 
    var fixture : ComponentFixture<DashboardComponent> = null; 
    beforeEach( 
     async( 
      ()=>{ 
       TestBed.configureTestingModule(
        { 
        imports: [ NgModule, RouterModule, FormsModule ], 
        declarations: [ DashboardComponent, PatientListComponent, PatientListItemComponent ], 
        providers: [ 
         { provide: AuthenticationService, useClass: MockAuthService }, 
         { provide: PatientSummaryService, useClass: MockPatientSummaryService }, 
         { provide: Router, useClass: RouterStub } 
           ] 
        } 
       ).compileComponents(); 
    })); 
    beforeEach(()=>{ 
     fixture = TestBed.createComponent(DashboardComponent); 
    }); 
    it("has a test",()=>{ expect(1).toBe(1);}); 
    /* 
    describe("filter section behavior",()=>{}); 
    describe("list display behavior", async(()=>{ 
     describe("filtered-list behavior", async(()=>{ 
      //component.filterList = true; 
      fixture.detectChanges(); 
      fixture.whenStable().then((done:any)=>{ 
       debugger; 
      }); 
     })); 
     describe("unfiltered-list behavior",()=>{ 
      //component.filterList = false; 
     }); 
    })); 
    */ 
}); 

應用程序/儀表板/ dashboard.component

import { Component, Input }  from '@angular/core'; 
import { Router }     from '@angular/router'; 

import { Authentication }   from '../authentication'; 
import { AuthenticationService } from '../authentication.service'; 
import { PatientSummaryService } from '../patient-summary/patient-summary.service'; 
import { PatientSummary }   from '../patient-summary/patient-summary'; 

@Component(
    { 
     moduleId: module.id, 
     selector: 'dashboard', 
     template: ` 
     <div class="container" *ngIf="credentials.valid"> 
      <div class="col-xs-12 filterOptions"> 
       <span class="col-xs-12"> 
        <button class="btn btn-small btn-default pull-right" (click)="toggleFilterView()">Toggle Filters</button> 
        <h4>Filter Options</h4> 
       </span> 
       <span *ngIf="viewFilters"> 
        <label> 
        <input type='checkbox' [(ngModel)]="filterList" /> 
        Filter the list for <strong>only</strong> patients linked to your account. 
        </label> 
        <div class="form-group"> 
         <label>Filter By Patient Name</label> 
         <input class="form-control" [(ngModel)]="nameFilter" placeholder="Patient name in full or in part." /> 
        </div> 
       </span> 
      </div> 
      <h1>Priority Patients</h1> 
      <patient-list [sourceData]="todaysPatientList | staffFilter : acceptableStaff" (clickPatient)="selectPatient($event)"></patient-list> 
      <h1>Patients Records <small>(Not Yet Complete)</small></h1> 
      <patient-list [sourceData]="nonActivePatientList | staffFilter : acceptableStaff" (clickPatient)="selectPatient($event)"></patient-list> 
     </div>`, 
     styles: [ 
       `.filterOptions { 
        background-color: hsla(187, 55%, 90%, 0.5); 
        padding: 1em; 
        border: solid 3px black; 
        border-radius: 1em; 
        margin-bottom: 1em; 
       }` 
      ] 
    } 
) 
export class DashboardComponent { 
    credentials : Authentication = new Authentication(null,null,null); 
    viewFilters: boolean = false; 
    nameFilter: string = ""; 
    filterList: boolean = true; 
    patientSummary: PatientSummary[]; 

    constructor(private patientSummaryService : PatientSummaryService, 
    private authService : AuthenticationService, 
    private router : Router){} 
    ngOnInit(){ 
     var app = this; 
     this.patientSummaryService.updatedList.subscribe(
      (list : PatientSummary[]) => {app.setPatientSummaryList(list);} 
     ); 
     this.authService.newCreds.subscribe(
      (creds : Authentication) => this.credentials = creds 
     ); 
     this.authService.invalidate.subscribe(
      (obj : any) => this.credentials = new Authentication(null,null,null) 
     ); 
    } 
    setPatientSummaryList(list: PatientSummary[]) { 
     var app = this; 
     list.sort((a: PatientSummary, b: PatientSummary) => { 
      var dateA = app.extractDate(a); 
      var dateB = app.extractDate(b); 
      if (dateA > dateB) return 1; 
      if (dateA < dateB) return -1; 
      return 0; 
     }); 
     this.patientSummary = list; 
    } 
    extractDate(item: PatientSummary) { 
     var date = item.arrivalTime; 
     if (date === null || date < item.visit.date) { 
      date = item.visit.date; 
     } 
     return date; 
    } 
    nameFilterFunction(item: PatientSummary) { 
     if (this.nameFilter == "") return true; 
     if (typeof item == "object" && typeof item.name != "undefined") { 
      var index = item.name.indexOf(this.nameFilter); 
      return (index !== -1); 
     } 
     return false; 
    } 
    toggleFilterView() { 
     this.viewFilters = !this.viewFilters; 
    } 


    /** 
    * Returns a list of patients in ascending order (oldest first) of items 
    * that are today and are assigned to a room. 
    */ 
    get todaysPatientList() { 
     var app = this; 
     if (!Array.isArray(this.patientSummary)) return []; 
     var list = this.patientSummary.filter(
      (item: PatientSummary) => { 
       var date = app.extractDate(item); 
       var now = new Date(); 
       var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 
       var tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); 
       return date >= today && date <= tomorrow; 
      }).filter((item: PatientSummary) => { 
       if (typeof item == "object" && typeof item.location == "object" && typeof item.location.room !== null) { 
        return item.location.room != "No Room Assignment"; 
       } else { 
        return true; 
       } 
      }); 
     return list.filter((item) => {return app.nameFilterFunction(item);}); 
    } 
    /** 
    * Returns a list of patients in descending order (most recent first) of items 
    * that do not appear in the todaysPatientList attribute; 
    */ 
    get nonActivePatientList() { 
     if (!Array.isArray(this.patientSummary)) return []; 
     var app = this; 
     var list = this.todaysPatientList; 
     var nonActiveList = this.patientSummary.filter((obj: PatientSummary) => { 
      var index = list.indexOf(obj); 
      return (index == -1); 
     }); 
     nonActiveList.reverse(); 
     return nonActiveList.filter((item) => {return app.nameFilterFunction(item);});; 
    } 
    get acceptableStaff() { 
     if (!this.filterList) { 
      return "any"; 
     } else { 
      var user = "any"; 
      if (this.credentials instanceof Authentication) { 
       user = this.credentials.username; 
      } 
      if (user === null) user = "any"; 
      return user; 
     } 
    }; 
    selectPatient(patient : PatientSummary){ 
     var id = patient.medfaceId; 
     this.router.navigate(['/detail',id]); 
    } 
} 

更新:2017年2月9日下午3點10分PCT

我認爲問題出在我的配置或我的代碼庫中。爲此,我今天嘗試使用Spies而不是MockClasses。通過使用Spies,我希望消除因錯誤供應商而產生的複雜問題。系統在瀏覽器中測試時加載,所以我知道我創建的常規提供程序工作正常,沒有加載錯誤。

使用Spies並未解決問題。我仍然得到DecoratorFactory錯誤。我會繼續嘗試新的解決方案,直到找到答案。任何援助表示讚賞。

+1

這個錯誤意味着你正在向NgModule中導入一些不是你可以導入的NgModule的東西。你可能已經知道這一點,但這就是錯誤所指的。它作爲裝飾工廠錯誤而表現的原因是Angular不直接使用裝飾器,而是使用裝飾器工廠,例如Input()而不是Input,這可能會讓人困惑,因爲它的文檔將這些引用爲裝飾器。我沒有在代碼中看到對DynamicTestModule的引用,因此它必須是傳遞依賴關係。 –

+0

謝謝!實際上,我不知道。我是Angular的新手,所以我閱讀和聽到的幾乎所有東西似乎都是新的信息。一切都有用,謝謝! –

回答

2

看起來這個問題很可能是由這條線引起的。甚至如果沒有,它可能是錯誤的

imports: [ NgModule, RouterModule, FormsModule ], 

注意NgModule一個角2模塊,但一個裝飾工廠,返回一個配置裝飾這反過來表示目標類作爲角2模塊。將它作爲Angular 2模塊導入可能會引發此錯誤。

+0

我會再試一次並刪除NgModule。 –

+0

這個伎倆!刪除NgModule後,我收到了一些額外的錯誤,但它們很容易修復。 NgModule導致了我所看到的錯誤,正如您所預測的那樣。謝謝。 –

+1

我很高興有幫助 –