2

I am writing some tests for an angular 2 RC application and I'm having some issues with the testing of observables. I mocked up the method setting it's type as observable but when the unit being tested tries to subscribe to the mocked observable I get an error 'Cannot read property 'subscribe' of undefined'

I'm testing my DashboardComponent, which injects a model3DService and calls model3DService.get3DModels() which is an observable that does an http request and returns an array of 3D model objects.

Here's some sample code:

Dashboard Component

import { Model3DService } from '../../services/model3D/model3D.service';
import { ProjectService } from '../../services/project/project.service';

@Component({
  selector: 'cmg-dashboard',
  styles: [require('./css/dashboard.scss')],
  template: require('./dashboard.html')
})
export class DashboardComponent implements OnInit {
  constructor(
    private projectService: ProjectService,
    private model3DService: Model3DService
  ) { }

  ngOnInit (): void {
    this.model3DService.get3DModels().subscribe((res: any[]) => {
      this.findProjects(res);
      this.models = res;
      this.projectService.isProjectSelected = true;
      this.createProject(res[0]);
    });
  }
}

Model3DService

@Injectable()
export class Model3DService {
 private models: any[] = [];

 public get3DModels (): Observable<any> {
    return this.http.get('../../../json/3DModel.json')
    .map(( res: Response ) => {
      this.models = res.json();
      return this.models;
    });
  }
}

Okay now that we have the under test heres the test I'm writing.

Dashboard Component Spec

class MockModel3DService {
  public get3DModels(): Observable<any> {
    return;
  }
}

describe('Dashboard Component', () => {
  beforeEachProviders(() => {
    return [
      DashboardComponent,
      provide(ProjectService, {
        useClass: MockProjectService
      }),
      provide(Model3DService, {
        useClass: MockModel3DService
      })
    ];
  });

  describe('ngOnInit', () => {
    it('should call model3DService.get3DModels on init', (inject([DashboardComponent], (dashboardComponent: DashboardComponent, model3DService: MockModel3DService) => {
      dashboardComponent.ngOnInit();
      expect(model3DService.get3DModels).toHaveBeenCalled();
    })));
  });
});
efarley
  • 8,371
  • 12
  • 42
  • 65

2 Answers2

3

The concept is similar to testing AngularJS $q promise. Stubbed method returns an observable mock. The method can return a subject instead which inherits Observable but also has properties of both observables and observers.

A fresh subject can be provided with mocked value in-place, a mocked promise would be required to be defined beforehand (subjects share this property with deferreds, see the relevant question).

RxJS 4 subjects have hasObservers method which obviates subscribe spy. RxJS 5 subjects miss the method, yet they expose observers property.

Most likely it should be something like that

let subject: Subject;

class MockModel3DService {
  public get3DModels(): Observable<any> {
    return subject;
  }
}

...
// beforeEach(...)
subject = new Subject;
...

// it(...)
const models = ['mocked'];
dashboardComponent.ngOnInit();

expect(subject.observers.length).toBe(1);

subject.next(models);
expect(model3DService.get3DModels).toHaveBeenCalled();
expect(dashboardComponent.models).toBe(models);
...
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
1

Your MockModel3DServic.get3DModels does not return a observable.

import { of } from 'rxjs';

class MockModel3DService { 
  public get3DModels(): Observable<any> {
   return of(yourResponse)
 }
}
Brendan B
  • 322
  • 2
  • 8