3

I am getting an error expected Spy but got undefined in my angular 10 test. I am using Jasmine and Karma. I have mocked the service SpreadsheetService as mockSpreadSheetService. I am calling the its method. I fail to understand why its expecting a spy.

One possible reason i could think of is that mockSpreadSheetService gets created before running the test and we are calling component.ngOnit which possible recreates the component instance. I tried to explicitly create the mockservice after component.ngOnit but it still didnt work.

My question Ideally we should be testing on the mock object. Why is spy needed.

expect(mockSpreadSheetService.loadXML).toHaveBeenCalled();

TestComponent

 let component: SpreadsheetComponent;
  let fixture: ComponentFixture<SpreadsheetComponent>;
  let mockSpreadSheetService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports: [HttpClientTestingModule],
        declarations: [SpreadsheetComponent],
        providers: [{SpreadsheetService, useFactory: () =>   mockSpreadSheetService.Object}]
      })
      .compileComponents();
  }));

  beforeEach(() => {

    mockSpreadSheetService = new Mock<SpreadsheetService>({
      loadXML: () => of(xmlData)
    });

    fixture = TestBed.createComponent(SpreadsheetComponent);
    component = fixture.componentInstance;
    //fixture.detectChanges();

  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call loadXML when ngOnInit is called', fakeAsync(() => {
   // spyOn(component.spreadsheetService, 'loadXML');
    component.ngOnInit();
    tick();
    expect(mockSpreadSheetService.loadXML).toHaveBeenCalled();
  }));
});

Main component

 ngOnInit(): void {
    this.spreadsheetService.loadXML().subscribe((data) => {
      this.parseXML(data)
        .then((data) => {
          console.log('data is ' , data);
          this.xmlItems = data;
        });
    });
  }
Tom
  • 8,175
  • 41
  • 136
  • 267
  • You commented out `// spyOn(component.spreadsheetService, 'loadXML');` so no spy is been called but in your test you are using `toHaveBeenCalled()` method. uncomment it should work – Kamran Khatti Sep 27 '20 at 07:33

1 Answers1

1

spy is not correctly created.

Here's how it would look:

let component: SpreadsheetComponent;
  let fixture: ComponentFixture<SpreadsheetComponent>;
  let mockSpreadSheetService;

  beforeEach(async(() => {
    mockSpreadSheetService = jasmine.createSpyObj(['loadXML']);
    mockSpreadSheetService.loadXML.and.returnValue(of());

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      declarations: [SpreadsheetComponent],
      providers: { provide: SpreadsheetService, useValue: mockSpreadSheetService }
    });

    fixture = TestBed.createComponent(SpreadsheetComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

  }));

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call loadXML when ngOnInit is called', () => {
    component.ngOnInit();
    expect(mockSpreadSheetService.loadXML).toHaveBeenCalled();
  });
});
  • I have uncommented the spyOn in my code but still get Error: : Expected a spy, but got undefined. – Tom Sep 27 '20 at 09:48
  • Did you spied on `mockSpreadSheetService` or `component.spreadsheetService`? You have to spy on `mockSpreadSheetService` since, you're expecting that to have been called. – Suyash Gupta Sep 27 '20 at 10:11
  • i mocked on mockSpreadSheetService and getting Error: : loadXML() method does not exist – Tom Sep 27 '20 at 10:13
  • it('should call loadXML when ngOnInit is called', fakeAsync(() => { spyOn(mockSpreadSheetService, 'loadXML').and.returnValue(of(xmlData)); component.ngOnInit(); tick(); expect(mockSpreadSheetService.loadXML).toHaveBeenCalled(); })); – Tom Sep 27 '20 at 10:13
  • I also tried spyOn(mockSpreadSheetService, 'loadXML()').and.returnValue({ subscribe: () => {} }); – Tom Sep 27 '20 at 10:25
  • Okay, I think I see it now. Why do you have two `beforeEach()`, not that it's wrong but you're providing the mockedService before it's created. Try moving the providers after you have created the mocked service. – Suyash Gupta Sep 27 '20 at 10:27
  • i thought so but when i tried it('should call loadXML when ngOnInit is called', fakeAsync(() => { spyOn(mockSpreadSheetService, 'loadXML').and.returnValue(of(xmlData)); component.ngOnInit(); mockSpreadSheetService = new Mock({ loadXML: () => of(xmlData) }); tick(); expect(mockSpreadSheetService.loadXML).toHaveBeenCalled(); })); – Tom Sep 27 '20 at 10:38
  • No, you're not doing it right. Let me post the code in the answer. – Suyash Gupta Sep 27 '20 at 10:53
  • @Tom Edited my answer. Can you try that? – Suyash Gupta Sep 27 '20 at 10:59
  • Sure . I have a question. Shouldnt you spyOn mockSpreadSheetService instead of component.spreadsheetService – Tom Sep 27 '20 at 11:01
  • @Tom Yes, that's what copy-paste do. Updated. – Suyash Gupta Sep 27 '20 at 11:04
  • I tried your suggestion. still getting : loadXML() method does not exist – Tom Sep 27 '20 at 11:06
  • @Tom Sorry, I didn't notice the way you're mocking the service. You're doing the C# way, that's not how we do it in angular. Update the code. This should work now. – Suyash Gupta Sep 27 '20 at 11:15
  • @Tom Also, I don't think you need `fakeAsync` and `tick`. Since you're spying on the service method, it won't get called truly. – Suyash Gupta Sep 27 '20 at 11:20
  • Now it works but with small change. With the way now it is done, SpOn is no longer needed as i get the message already spied. So commenting out SpyOn makes the test work – Tom Sep 27 '20 at 11:30
  • @Tom Interesting! – Suyash Gupta Sep 27 '20 at 11:34