Search code examples
rxjsangular2-servicesangular2-observablescombinelatest

How to write Jasmine Unit test case for combineLatest rxjs Angular


mycomponent.ts

import { Component, OnInit } from '@angular/core';
import {FormGroup,FormControl} from '@angular/forms'
import { DataServiceService } from './data-service.service';
import {combineLatest,Observable,pipe} from 'rxjs';
import {map,tap} from 'rxjs/operators';
import {Model} from './mode';
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

  constructor(private dataService: DataServiceService){}
  name = 'Angular';
  myForm: FormGroup;
  observableResult$: Observable<any>;

  ngOnInit(){
    this.myForm = new FormGroup({
      localId: new FormControl()
    })

  this.observableResult$ = combineLatest(
    this.myForm.get('localId').valueChanges,
    this.dataService.getDataFromURL(),
    (localIdSelected, dataFromAPI) => ({localIdSelected,dataFromAPI})).
    pipe(map(each => this.filterData(each.dataFromAPI,each.localIdSelected)));

    this.observableResult$.subscribe(value => {
      debugger
    })

  }
  filterData(dataFromAPI,localIDSelected){
    debugger
     return dataFromAPI.filter(item => item.userId > Number(localIDSelected));
    }

}

data.service.service.ts

import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http'
import {Model} from './mode';
import {Observable} from 'rxjs';
@Injectable()
export class DataServiceService {

  constructor(private http:HttpClient) { }

  getDataFromURL():Observable<Model>{
    return this.http.get<Model>('https://jsonplaceholder.typicode.com/todos');
  }

}

app.component.html

<form [formGroup]="myForm" >

<select formControlName="localId">
  <option>1</option>
  <option>2</option>
</select>

</form>

app.spec.ts

const spyFilter = spyOn(component as any, filterData).and.callThrough();

const constAPIData$ = staticDataServiceMock.getAPIData();
                    spyOn(staticDataServiceMock, 'getAPIData').and.returnValue(
                        observableOf(countryStaticData$)
                    );
component.myForm.get('localId').setValue(1);

component.observableResult$.subscribe(value => {
expect(value[0].id==21).toBeTrue();
});

staticDatamock.ts

export class StaticDataMock{

static getAPIData(): Observable<StaticDataElements[]> {
    return [
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },
  {
    "userId": 1,
    "id": 3,
    "title": "fugiat veniam minus",
    "completed": false
  },
  {
    "userId": 1,
    "id": 4,
    "title": "et porro tempora",
    "completed": true
  }];
  }
}

I have added my test case to cover the combineLatest operator anf filterData in app.spec.ts, but the required code fails. and my expectation to call filterData is failed. combineLatest will fire the event on a valueChange and get data from API. i can created mock and setValue inside spec file, still it is not working.


Solution

  • Ok, to try and help you get going on this, I went ahead and set up a Stackblitz with the data you have provided so far. Here is the link.

    I did a few things to get the test working (sort of).

    • I changed the method type of getAPIData() within the StaticDataMock class to public so you could call it from outside the class.
    • I had the method return an of(), turning the return value into an Observable of your data.
    • I guessed at your implementations of DataServiceService mocking, details in the Stackblitz.
    • I created a Jasmine spyObject to intercept the calls to getDataFromURL() in the service and return the observable you had created with StaticDataMock.
    • I rearranged the order of how things were called in the spec, and did a console.log() to show that component.observableResult$ never emits.

    Update

    As per the comments below, the Stackblitz link above has been updated and is now working. From that Stackblitz here is the working spec:

    it('should change return of service.function1() only', () => {
        fixture.detectChanges(); 
        component.observableResult$.subscribe(value => {
            console.log('observable Emitted, value is ', value);
            expect(value[0].id==1).toBe(true); 
        });
        component.myForm.get('localId').setValue(1);
    });
    

    The key to this was to set up the subscribe first, and then emit a new value in the form, which will update the the combineLatest() and execute the code inside the subscribe().

    I am glad this worked!