Search code examples
angulartypescriptunit-testingjasmineangular-unit-test

Why this Jasmine unit test always fail (testing an Angular component) also if the output returned by my method is correct?


first time for me trying debugging an Angular project with Jasmine.

I have this PeopleListComponent component class:

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { EventService } from '../event.service';
import interactionPlugin, { Draggable } from '@fullcalendar/interaction';

interface WorkShiftTypes {
  name: string;
  value: string;
}

@Component({
  selector: 'app-people-list',
  templateUrl: './people-list.component.html',
  styleUrls: ['./people-list.component.css']
})
export class PeopleListComponent implements OnInit {

  people: any[];

  //cities: City[];

  workShiftTypes: WorkShiftTypes[];
  selectedShift: WorkShiftTypes;

  @ViewChild('draggable_people') draggablePeopleExternalElement: ElementRef;

  constructor(private eventService: EventService) { }

  ngOnInit(): void {
    this.eventService.getPeople().then(people => {this.people = people;});

    this.selectedShift = {name: 'Mattina', value: 'Mattina'};

    this.workShiftTypes = [
      {name: 'Mattina', value: 'Mattina'},
      {name: 'Pomeriggio', value: 'Pomeriggio'},
      {name: 'Notte', value: 'Notte'},
      {name: 'Custom', value: 'Custom'}
    ];
  }

  ngAfterViewInit() {
    console.log("PEOPLE LIST ngAfterViewInit() START !!!")
    var self = this

    new Draggable(this.draggablePeopleExternalElement.nativeElement, {
      itemSelector: '.fc-event',
      eventData: function(eventEl) {
        console.log("DRAG !!!");
        //console.log("SELECTED SHIFT: " + self.selectedShift.value);
        return {
          title: eventEl.innerText,
          startTime: "17:00",
          duration: { hours: 8 }
        };
      }
    });

  }

  createEventObject() {
    console.log("createEventObject() START");
    return 1;
  }

}

As you can see it contains this simple method that, at the moment, simply returns the value 1:

createEventObject() { console.log("createEventObject() START"); return 1; }

I want to create a very simple Jasmine unit test asserting that the returned value from this method is 1. So I have implemented this people-list.component.spec.ts file:

import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { PeopleListComponent } from "./people-list.component"
import { EventService } from '../event.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';

describe('people-list', () => {
    let component: PeopleListComponent;
    let fixture: ComponentFixture<PeopleListComponent>;
    let eventServiceSpy : jasmine.SpyObj<EventService>;
        beforeEach(async(() => {
          const eventServiceSpyObj = jasmine.createSpyObj('EventService',['getPeople'])

          TestBed.configureTestingModule({
              declarations: [PeopleListComponent],
              imports: [HttpClientTestingModule],
              providers : [{ provide : EventService, useValue : eventServiceSpyObj }]

        });

        // Create a testing version of my PeopleListComponent:
        fixture = TestBed.createComponent(PeopleListComponent);
        //eventServiceSpy = TestBed.inject(EventService);
        eventServiceSpy = <jasmine.SpyObj<EventService>><any>TestBed.inject(EventService);
        component = fixture.componentInstance;
        fixture.detectChanges();
    }));

    it('createEventObject()  return 1', () => {
        console.log("TEST 1 START");
        var methodOutput = component.createEventObject();
        console.log("METHOD OUTPUT: " + methodOutput);
        expect(methodOutput).toBe(1);
        //expect(1).toBe(1);
    })
})

As you can see I have also inserted some console.log() statement into the test arrow function to check the output of the called component.createEventObject() and it is correctly return 1.

The problem is that the test fail and I can't understand why.

In the console I am obtaining this error message:

developer@developer-virtual-machine:~/Documents/Angular-WS/SOC-Calendar$ ng test
10% building 2/2 modules 0 active23 05 2020 09:27:06.907:WARN [karma]: No captured browser, open http://localhost:9876/
23 05 2020 09:27:06.916:INFO [karma-server]: Karma v5.0.5 server started at http://0.0.0.0:9876/
23 05 2020 09:27:06.920:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
23 05 2020 09:27:06.927:INFO [launcher]: Starting browser Chrome
23 05 2020 09:27:12.013:WARN [karma]: No captured browser, open http://localhost:9876/
23 05 2020 09:27:12.211:INFO [Chrome 81.0.4044.129 (Linux x86_64)]: Connected on socket 0_E-_vH-y-N_pe-NAAAA with id 36927658
WARN: ''p-selectButton' is not a known element:
1. If 'p-selectButton' is an Angular component, then verify that it is part of this module.
2. If 'p-selectButton' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.'
Chrome 81.0.4044.129 (Linux x86_64): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
WARN: ''p-selectButton' is not a known element:
1. If 'p-selectButton' is an Angular component, then verify that it is part of this module.
2. If 'p-selectButton' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to supWARN: ''p-orderList' is not a known element:
1. If 'p-orderList' is an Angular component, then verify that it is part of this module.
2. If 'p-orderList' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.'
Chrome 81.0.4044.129 (Linux x86_64): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
WARN: ''p-orderList' is not a known element:
1. If 'p-orderList' is an Angular component, then verify that it is part of this module.
2. If 'p-orderList' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppreLOG: 'TEST 1 START'
Chrome 81.0.4044.129 (Linux x86_64): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
LOG: 'createEventObject() START'
Chrome 81.0.4044.129 (Linux x86_64): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
LOG: 'METHOD OUTPUT: 1'
Chrome 81.0.4044.129 (Linux x86_64): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
Chrome 81.0.4044.129 (Linux x86_64) people-list createEventObject()  return 1 FAILED
    Failed: Cannot read property 'then' of undefined
        at <Jasmine>
        at PeopleListComponent.ngOnInit (http://localhost:9876/_karma_webpack_/src/app/people-list/people-list.component.ts:29:34)
        at callHook (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4770:1)
        at callHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4734:1)
        at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4674:1)
        at refreshView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11998:1)
        at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:12114:1)
        at tickRootContext (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13668:1)
        at detectChangesInRootView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13702:1)
        at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:15420:1)
        at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:331:1)
Chrome 81.0.4044.129 (Linux x86_64): Executed 1 of 1 (1 FAILED) (0 secs / 0.055 secs)
Chrome 81.0.4044.129 (Linux x86_64) people-list createEventObject()  return 1 FAILED
    Failed: Cannot read property 'then' of undefined
        at <Jasmine>
        at PeopleListComponent.ngOnInit (http://localhost:9876/_karma_webpack_/src/app/people-list/people-list.component.ts:29:34)
        at callHook (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4770:1)
        at callHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4734:1)
        at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:4674:1)
        at refreshView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11998:1)
        at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:12114:1)
        at tickRootContext (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13668:1)
        at detectChangesInRootView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13702:1)
        at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:15420:1)
        at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/tChrome 81.0.4044.129 (Linux x86_64): Executed 1 of 1 (1 FAILED) (0.105 secs / 0.055 secs)
TOTAL: 1 FAILED, 0 SUCCESS
TOTAL: 1 FAILED, 0 SUCCESS

I also tried to replace:

expect(methodOutput).toBe(1);

with:

expect(1).toBe(1);

to ensure the test success but I still obtain the same error so I think that the problem is not related to this excpect-toBe line but in some configuration of my test.

Why am I obtaining this strange error? What it exactly means? What is wrong with my code? How can I fix it?


Solution

  • I see several problems that could be described as the testing module is not configured properly.

    • The component contains several dependencies. Like p-selectButton, p-orderList, etc. Angular tries to resolve those and failed. Workaround - tell the Angular to avoid resolving those tags.
    TestBed.configureTestingModule({
      ...
      schemas:[NO_ERRORS_SCHEMA],
    });
    
    • The fixture.detectChanges() triggers component lifecycle events. ngOnInit among of those. And the error happens there:
        Failed: Cannot read property 'then' of undefined
            at <Jasmine>
            at PeopleListComponent.ngOnInit 
    

    There is only one then inside ngOnInit.

    this.eventService.getPeople().then(people => {this.people = people;});
    

    Now let's look into the provided mock:

    const eventServiceSpyObj = jasmine.createSpyObj('EventService',['getPeople'])
    { provide : EventService, useValue : eventServiceSpyObj }
    

    It seems like the created spy eventServiceSpyObj has the method getPeople(). Reading the error Cannot read property 'then' of undefined it is obvious that getPeople() return undefined instead of Promise. To deal with this issue you need to mock the the return value for getPeople

    const eventServiceSpyObj = jasmine.createSpyObj('EventService',['getPeople'])
    eventServiceSpyObj.getPeople.and.returnValue(Promise.resolve());
    

    or don't call the method fixture.detectChanges(). It is not needed for this test.