Reputation: 1065
I am creating a test for a component named 'DashboardComponent'. The test spec is proving hard to create. Karma is throwing the error "Unexpected value 'DecoratorFactory' imported by the module 'DynamicTestModule'".
I have tried removing code until the error disappears and then adding code back until it reappears to identify the source. However, this has not provided fruitful results, because the code breaks when using several different configurations, and I cannot tell which configuration causes the error to exist, and which causes the error to be thrown. I suspect that the bad configuration exists in all my non-failing attempts, and certain lines of code, like the addition of an it()
call, cause the error to be suddenly visible when it was not visible before.
It is my hope that someone with more experience than me will see my error and provide some advice. Others have had this same problem on StackOverflow, and the solutions that worked for them do not seem to apply to this situation.
Code Diagram
To make the code base easier to digest, I have created a UML diagram to show the dependancies that I am trying to replicate in the TestBed configuration.
The green element is the element being tested. The package elements are imported into the module, and the services each have a mock class, and we provide the mock classes in their place. We must declare three components, because the template of DashboardComponent refers to PatientListComponent, and the template of PatientListComponent relies on PatientListItemComponent.
Code & Files
It is my hope not to write a novel in this question, so I will add more files & code, if they are requested. Until they are requested, I will provide those files that seem most relevant to identifying the issue.
app/dashboard/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;
} );
}) );
*/
} );
app/dashboard/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;
} );
}) );
*/
} );
app/dashboard/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]);
}
}
Update: Feb 9, 2017 3:10pm PCT
I assume that the problem is in my configuration or my code base. For this reason, I attempted today to use Spies instead of MockClasses. By using Spies, I hoped to remove the complications created by having faulty providers. The system loads when testing in the browser, so I know that the regular providers that I have created work well and without load errors.
Using Spies did not fix the problem. I still get the DecoratorFactory error. I will continue to attempt new solutions until an answer is found. Any assistance is appreciated.
Upvotes: 0
Views: 954
Reputation: 31823
It looks like the issue may well be caused by this line. Even if not, it is likely an error
imports: [ NgModule, RouterModule, FormsModule ],
Note that NgModule
is not an Angular 2 Module but a decorator factory that returns a configured decorator which in turn denotes the target class as an Angular 2 module. Importing it as an Angular 2 module would likely raise this error.
Upvotes: 2