2

I am trying to implement unit test in Angular for the first time, I am using Jasmine. I came from Java\Spring and using Spring framework it automatically inject all the dependencies into my test but I have no idea about how to do it using Angular Unit Test with Jasmine.

I try to explain better what I have to do.

1) 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() {
    return 1;
  }

}

As you can see this component contains this very minimalistic method createEventObject():

createEventObject() { return 1; }

that at the moment return only the value 1 (I want keep it as simple possible to try my unit test, then I will implement the real use case).

Ok, so I created this people-list-spec.ts file that will contains the unit test for the previous method:

import { PeopleListComponent } from "./people-list.component"
import { EventService } from '../event.service';

describe('people-list', () => {

  let eventService = new EventService();
  let component = new PeopleListComponent(eventService);

  it('createEventObject()  return 1', () => {

  })
})

Ok, so what I have to do is obtain a PeopleListComponent instance, call on it the createEventObject() and check that the returned value is 1 to assert that the unit test success.

And here I have the following problem: PeopleListComponent constructor take a EventService paramether. Ok...I can build it and pass it but then the EventService constructor take an HttpClient parameter.

All these dependencies injection are automatically handled by Angular in my project and I suspect that manually creation is not the right choise (a lot of code repetition and moreover HttpClient class should be handled by Angular).

What is the solution for this problem? Using Spring the framework handle the dependency injection also for the unit test project, I think (and hope) that in Angular\Jasmine is the same, but what have I to do exactly?

AndreaNobili
  • 40,955
  • 107
  • 324
  • 596
  • You want to read up on Jasmine mocks and spies. You absolutely do not want to recreate dependencies in your test, don't want to directly inject dependencies, and don't want to use external services directly - mock them. Mock the service/external class, spy on it, and inject your expected results. This page is a quickstart https://jasmine.github.io/2.0/introduction.html but there are a lot of resources on mocking and spying. Also, please don't try to test lifecycle hooks like ngOnInit. – Jason Woods May 23 '20 at 12:34

2 Answers2

3

Angular generates spec file automatically for you. If it do not exists no problem. Lets see how it will look like :

import { PeopleListComponent } from "./people-list.component"
import { EventService } from '../event.service';

describe('people-list', () => {

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

    TestBed.configureTestingModule({
      declarations: [ PeopleListComponent ],
      providers : [{ provide : EventService, useValue : eventServiceSpyObj }]
    })
    .compileComponents();
    fixture = TestBed.createComponent(PeopleListComponent);
    component = fixture.componentInstance; 
   eventServiceSpy = TestBed.inject(EventService);
  }));


  it('createEventObject()  return 1', () => {
       eventServiceSpy.getPeople.and.returnValue(of(1));
       component.ngOnInit();
       console.log(component.people); // this value should be one as return in mock service.
  })
})

There can be error for imports and all. But this will be the general structure of the method There can be error in the beforeEach function while creating components. Please let me know the errors.

pbmahadik
  • 164
  • 6
0

Try this

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 }]

        });
        fixture = TestBed.createComponent(PeopleListComponent);
        eventServiceSpy = TestBed.inject(EventService);
        component = fixture.componentInstance;
        fixture.detectChanges();
    }));

    it('createEventObject()  return 1', () => {
        expect(component.createEventObject()).toBe(1)
    })
})

You can test ngOnInit method this way,

it('ngOnInit should initialise people variable', () => {
     const mockPeopleList = [{age: 24, gender: "male"}];

     const fakePromise = new Promise(function(resolve, reject) {
       resolve(mockPeopleList)
     });

      eventServiceSpy.getPeople.and.returnValue(fakePromise);

     component.ngOnInit();

     expect(component.people).toEqual(mockPeopleList)
})
Shijil Narayanan
  • 1,011
  • 8
  • 21