I am trying to write a test for a component method that uses Observable.forkJoin. I have been all over the web and down some marbles rabbit holes, but in the end I think all I really need to do is mock the Observable forkJoin call and return fake data. Here is my component method
public loadData(): void {
this.someOtherMethod();
this.someProperty = false;
this.someOtherMethod2();
if (this.isNew) {
this.noData = true;
} else if (this.key) {
Observable.forkJoin([
/*00*/ this.service1$.someCall(this.key),
/*01*/ this.service2$.someCall(this.key),
/*02*/ this.service2$.someCall1(this.key),
/*03*/ this.service2$.someCall2(this.key),
/*04*/ this.service2$.someCall3(this.key),
/*05*/ this.service2$.someCall4(this.key),
/*06*/ this.service2$.someCall5(this.key),
])
.takeWhile(() => this.alive)
.subscribe(
response => {
... // join all the data together
},
// error => this.handleError(error)
);
}
this.changeDetector$.markForCheck();
}
Here is my test so far:
it('makes expected calls', async(() => {
const response = [];
const service1Stub: Service1 = fixture.debugElement.injector.get(Service1 );
const service2Stub: Service2 = fixture.debugElement.injector.get(Service2 );
comp.key = key;
spyOn(comp, 'someOtherMethod').and.returnValue(of(response));
spyOn(comp, 'someOtherMethod2').and.returnValue(of(dummyData));
spyOn(service1Stub, 'someCall').and.returnValue(of(dummyData));
spyOn(service2Stub, 'someCall').and.returnValue(of(response));
spyOn(service2Stub, 'someCall1').and.returnValue(of(response));
spyOn(service2Stub, 'someCall2').and.returnValue(of(response));
spyOn(service2Stub, 'someCall3').and.returnValue(of(response));
spyOn(service2Stub, 'someCall4').and.returnValue(of(response));
spyOn(service2Stub, 'someCall5').and.returnValue(of(response));
comp.loadData();
expect(comp.someProperty).toBe(false);
expect(comp.someOtherMethod).toHaveBeenCalled();
expect(comp.someOtherMethod2).toHaveBeenCalled();
expect(service1Stub.someCall).toHaveBeenCalledWith(key);
expect(service2Stub.someCall1).toHaveBeenCalledWith(key);
expect(service2Stub.someCall2).toHaveBeenCalledWith(key);
expect(service1Stub.someCall3).toHaveBeenCalledWith(key);
expect(service1Stub.someCall4).toHaveBeenCalledWith(key);
expect(service1Stub.someCall5).toHaveBeenCalledWith(key);
}));
I get the following error (after I comment out the error catch above):
TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Marbles seems to be concerned with testing the observable and how it reacts. I just want to see that the calls are being made and do deeper testing into what is happening inside the subscribe where all the data is joined together.
I know there are better ways to handle data but that requires a large overhaul of the application. I cannot change the method, just have to live with how it is now.
A bit clueless here, it works for me in this reproduction:
import { ComponentFixture, TestBed, } from '@angular/core/testing';
import { Component, Injectable, OnInit } from '@angular/core';
import { of, forkJoin } from 'rxjs';
@Injectable({providedIn: 'root'})
export class Service { // service to be mocked
response = ['hello', 'world'];
// this is mocked anyway
someCall() { return of(this.response); }
someCall1() { return of(this.response); }
someCall2() { return of(this.response); }
someCall3() { return of(this.response); }
someCall4() { return of(this.response); }
someCall5() { throw new Error('oups!') }
}
@Component({
selector: 'app-testee',
template: `<h1>hi</h1>`
})
export class TesteeComponent implements OnInit { // main comp
constructor(private service: Service) {}
result: string; // aggregated property
ngOnInit() { }
loadData() {
forkJoin([
this.service.someCall(),
this.service.someCall1(),
this.service.someCall2(),
this.service.someCall3(),
this.service.someCall4(),
this.service.someCall5(),
]).subscribe(
rse => this.result = [].concat(...rse).join('-'), // aggregation
err => this.result = `ERR ${err}`
);
}
}
describe('TesteeComponent', () => {
let fixture: ComponentFixture<TesteeComponent>;
let component: TesteeComponent;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
TesteeComponent
],
providers: [
Service
]
}).compileComponents();
fixture = TestBed.createComponent(TesteeComponent);
component = fixture.componentInstance;
});
it('should load data', () => {
const testee = component;
// why not use TestBed.get(Service) here?
const service = fixture.debugElement.injector.get(Service);
spyOn(service, 'someCall').and.returnValue(of(['test', 'values', '0']));
spyOn(service, 'someCall1').and.returnValue(of(['test', 'values', '1']));
spyOn(service, 'someCall2').and.returnValue(of(['test', 'values', '2']));
spyOn(service, 'someCall3').and.returnValue(of(['test', 'values', '3']));
spyOn(service, 'someCall4').and.returnValue(of(['test', 'values', '4']));
spyOn(service, 'someCall5').and.returnValue(of(['test', 'values', '5']));
expect(testee).toBeTruthy();
fixture.detectChanges();
testee.loadData();
expect(testee.result).toEqual('test-values-0-test-values-1-test-values-2-test-values-3-test-values-4-test-values-5');
expect(service.someCall).toHaveBeenCalled();
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With