Ad

Cannot Read Property 'doc' Of Undefined When Mocking Service Class Through Spy/karma

I am trying to test my angular application through Karma. My application is connected to a firebase firestore database. I am trying to mock a collection and use this to test my component functions.

The code snippets that I am using are the following:

sprint.service.ts:

export class SprintService {

  getSprints() {
    return this.firestore.collection('sprints').snapshotChanges();
  }
  constructor(private firestore: AngularFirestore) { }
}

sprints.component.ts

sprints : Sprint[];

constructor(private sprintService: SprintService) { }

ngOnInit() {
    this.sprintService.getSprints().subscribe(data => {
      this.sprints = data.map(e => {
        return {
          id: e.payload.doc.id, //HERE IT ERRORS
          ...e.payload.doc.data()
        } as Sprint;
      })
    });
  }

sprints.component.spec.ts

//Mock class
class MockSprintServce
{
  getSprints(){
    return of([
      {id: "1", name:"TestSprint", description:"TestSprint", startDate:new Date(2000, 0, 1), endDate:new Date(2001, 0, 1), isActive:true},
      {id: "2", name:"TestSprint2", description:"TestSprint2", startDate:new Date(2000, 0, 1), endDate:new Date(2001, 0, 1), isActive:false},
    ])
    }
}

beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ FormsModule, AngularFireModule.initializeApp(environment.firebase) ],
      declarations: [ ArchivedUserstoriesComponent,SprintDetailComponent, SprintsComponent, UserDetailComponent, UsersComponent, UserstoriesComponent, UserstoryDetailComponent ],
      providers: [AngularFirestore, {provide: SprintService, useClass: MockSprintServce}]
    })
    .compileComponents();
  }));

beforeEach(() => {
    app.sprints = [
      {id: "1", name:"TestSprint", description:"TestSprint", startDate:new Date(2000, 0, 1), endDate:new Date(2001, 0, 1), isActive:true},
      {id: "2", name:"TestSprint2", description:"TestSprint2", startDate:new Date(2000, 0, 1), endDate:new Date(2001, 0, 1), isActive:false},
    ]
  });


it(`should return all Sprints`, async(() => {

    //arrange
    let getSpy = spyOn(mockSprintServiceObject, 'getSprints').and.returnValue({ subscribe: () => {} });

    //act
    app.ngOnInit({});

    //assert
    expect(getSpy).toHaveBeenCalled();
    expect(getSpy).toContain(app.sprints[1]);
  }));

I want the code to be working without any errors. I probably think i have to rewrite the getSprints method in my MockSprintService. Does anybody know what i should return or generate in the getSprints() method to make my ngOnInit work again? Help would be appreciated.

Ad

Answer

I see you are importing and initialising AngularFireModule in your dynamic test module. Which means that it actually connects to firebase backend every time your run your test.. which usually is a very bad idea. What if your test cases need to test editing or removing entries? That would mean they would do it on actual data each time.

Ideally you want to mock all your dependencies and avoid importing real ones as much as possible (I know in Angular world it is not always possible).

One solution I have found for myself is to use ts-mockito library. It allows you to mock classes with ease it often it works out of the box. There's a blog post I wrote some time ago, if you'd like to learn more: Mocking with ts-mockito

...

Back to your concrete example. It looks like your mock data shape doesn't match what firebase service returns.

      this.sprints = data.map(e => {
        return {
          id: e.payload.doc.id, //HERE IT ERRORS
          ...e.payload.doc.data()
        } as Sprint;
      })

You map every data item and it is expected to have a payload object with doc object, which has id property and data() method.

However, in your MockSprintServce, you return an observable with array of items with a shape:

{
  id: "1",
  name:"TestSprint",
  description:"TestSprint",
  startDate:new Date(2000, 0, 1),
  endDate:new Date(2001, 0, 1),
  isActive:true
}

Is simply doesn't match. If you want to continue with current setup and have it work, try changing the items in your

  getSprints(){
    return of(...

to

[{
  payload: {
    doc: {
      id: '1',
      data: () => ({id: "1", name:"TestSprint", description:"TestSprint", startDate:new Date(2000, 0, 1), endDate:new Date(2001, 0, 1), isActive:true})
    }
  }
}]

Ad
source: stackoverflow.com
Ad