12

I'm trying to unit-test an array that is set from an async service in the ngOnInit() function of my component.

export class DashboardComponent implements OnInit {
  todos: Todo[] = [];

  constructor(public todoService: TodoService) {}

  ngOnInit(): void {
    this.todoService.getTodos()
      .then(todos => this.todos = todos.slice(1, 5));
  }
}

When I try to test this with a function like that:

  it('should load four todos for dashboard', function(done) {
    expect(component.todos.length).toEqual(4);
  });

I get an error that 0 is not equal to 4, as the promise is not resolved yet. The only way I could get this to work is by making the service public and running "dirty" code like that:

  it('should load four todos for dashboard', function(done) {
    var todos = component.todoService.getTodos();
    todos.then(x => {
      expect(x.length).toEqual(6);
      expect(component.todos.length).toEqual(4)
    });
    done();
  });

But there sure is a better and cleaner way to do this, so any suggestings for improvements are welcome!

Edit 1: After the hint of dbandstra, which points in the right direction, I came up with this code:

describe('DashboardComponent', () => {
  beforeEach( async(() => {
    TestBed.configureTestingModule({
      declarations: [
        DashboardComponent, EmptyComponent,
        RouterLinkStubDirective, RouterOutletStubComponent
      ],
      providers: [{provide: TodoService, useClass: FakeTodoService}]
    })
      .overrideComponent(TodoSearchComponent, EmptyComponent)
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(DashboardComponent);
        comp    = fixture.componentInstance;
      });
  }));

  beforeEach(() => {
    // TodoService actually injected into the component
    let todoService = fixture.debugElement.injector.get(TodoService);

    let spy = spyOn(todoService, 'getTodos')
      .and.returnValue(Promise.resolve(todos));

    // trigger initial data binding
    fixture.detectChanges();
  });

  it('should load four todos for dashboard', () => {
    fixture.whenStable().then(() => { // wait for async getTodos
      fixture.detectChanges();        // update view with todos
      expect(comp.todos.length).toEqual(4);
    });
  })
});

This works like a charm, thanks!

Edit 2: I also got it to work with the suggested solution from https://stackoverflow.com/a/39445420/3368833 by making the fake service synchronous like this:

class TodoServiceMock{
  private data: Todo[];

  getTodos() {
    let todos = [ ...insert fake data here... ];
    this.data = todos;
    return this;
  }

  then(callback) {
    callback(this.data);
  }
}
Ralf
  • 160
  • 1
  • 8
  • 2
    Explained here (their example is almost identical to your code) https://angular.io/docs/ts/latest/guide/testing.html#!#component-with-async-service – dbandstra May 31 '17 at 21:37
  • The `whenStable` is wat fixed my async init tests, thanks for the code example! – thomaux Aug 24 '22 at 09:07
  • You often can also use the `fakeAsync` zone and `tick` instead of whenStable. It's documented here: https://angular.io/api/core/testing/fakeAsync#description – jlang Dec 16 '22 at 08:15

0 Answers0