Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test *ngFor in Angular 2?

I am new to Angular and created To-Do Application. I tried to find the solution at many places even the Testing documentation
I am testing a component. That bring data from mongodb, I have mocked that data though.

The following is the HTML of template (using bootstrap):

<li class="list-group-item" *ngFor="let task of tasks">
    <div class=" task-date">{{task.date}}</div>
</li>

The below is the component code (I'm adding only sub-part):

export class ShowTaskComponent extends OnInit{

tasks:Task[];

ngOnInit() {
  setInterval(() => {this.refreshTasks()}, 3000);
}

constructor(private appService: AppService, private _router:Router){
  super();
  this.refreshTasks();
}

refreshTasks = function() {

  this.appService.retrieveDataFromServer().subscribe((data: any) => {

    //this.appService.taskArray = data;

    this.tasks = data;

  });

};
}

Now I'm adding the test code (.spec.ts file's sub part) ... To explain well, all of the code (excluding imports):

  let comp: ShowTaskComponent;
  let fixture: ComponentFixture<ShowTaskComponent>;
  let service: AppService;
  let router: Router;

  let debugTaskTitle: DebugElement[];
  let taskTitles: any[];

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ShowTaskComponent],
      providers: [AppService],
      imports: [....]
    })
      .compileComponents();
  }));

  beforeEach( () => {

    fixture = TestBed.createComponent(ShowTaskComponent);
    service = fixture.debugElement.injector.get(AppService);
    router = fixture.debugElement.injector.get(Router);

    let dummyTasks: any[] = [
      {
        "date": "12 October 2017",
        "title": "Demo task 1",
        "description": "Demo task 1 desc",
        "priority" : "High",
        "_id" : "1"
      },
      {
        "date": "1 January 2018",
        "title": "Demo task 2",
        "description": "Demo task 2 desc",
        "priority" : "Low",
        "_id" : "2"
      }
    ];

    spyOn(service, 'retrieveDataFromServer').and.returnValue(Observable.of<any[]>(dummyTasks));


    fixture = TestBed.createComponent(ShowTaskComponent);
    comp = fixture.componentInstance;
    console.log("----------------------------> comp ", comp);

    debugTaskTitle = fixture.debugElement.queryAll(By.css('div.task-title'));
    console.log("----------------------------> debugTaskTitle ", debugTaskTitle);

    let counter: number = 0;

    for(let title of debugTaskTitle) {
      taskTitles.push(title.nativeElement);
      taskTitles[counter].innerText = dummyTasks[counter].title;
      counter ++;
    }
  });

  //This test fails because taskTitles is undefined!
  it('test whether a correct test has loaded', () =>{

    expect(taskTitles).toBe(["Demo task 1", "Demo task 2"]);
  });

The problem comes with debugTaskTitle. This is empty array, when I try to initialise it with fixture.debugElement.queryAll(By.css()).

I tried to print it on console (look at long --------> arrows) and it is empty array.

The object of the component comp is created and is doing great.

Can anybody suggest me where I'm wrong? I'm late in submitting this assignment by three days. Any help will be great.

Whole code is at github repo

Also, I don't see anything on browser when I run only first test. It should show tasks on browser!

So does that mean *ngFor doesn't populate the content on page once .compileComponents(); has been called to generate CSS and HTML within first beforeEach()?

like image 577
ashishtomer Avatar asked Mar 03 '17 02:03

ashishtomer


1 Answers

Since retrieveDataFromServer returns an Observable, you need to wait for the async task to resolve. For that you can wrap the beforeEach in async, just like the one above it. Then you need to wait for it to stabilize

fixture = TestBed.createComponent(ShowTaskComponent);
comp = fixture.componentInstance;

fixture.whenStable().then(() => {
    // after something in the component changes, you should detect changes
    fixture.detectChanges();

    // everything else in the beforeEach needs to be done here.
    debugTaskTitle = fixture.debugElement.queryAll(By.css('div.task-title'));
    ...
})

UPDATE

Note that it is now recommended to use fakeAsync over async now. Unless the test makes an XHR request, which fakeAsync cannot handle, fakeAsync is the preferred method to use when testing asynchronous code. What fakeAsync does is allow you to write code as if it was synchronous and let you control when to complete the async test. Some test code might look something like

import { fakeAsync } from '@angular/core/testing';

it('should...', fakeAsync(() => {
  fixture = TestBed.createComponent(ShowTaskComponent);
  comp = fixture.componentInstance;

  comp.callSomeAsyncMethod();
  tick();

  fixture.detectChanges();
  // assertions
}));
like image 65
Paul Samsotha Avatar answered Oct 20 '22 11:10

Paul Samsotha