I have a component that calls a service to see if a subscription has been announced from another component.
Component:
this.activateProcessReadySubscription = this.returnService.processReadySubscriptionAnnouced$.subscribe(
itemsInCart => {
this.itemsInCart = itemsInCart;
});
When I try to test this I get an error:
TypeError: Cannot read property 'subscribe' of undefined
SPEC
it('should call constructor', fakeAsync(() => {
mockReturnsService.setResponse(0, true);
tick();
fixture.detectChanges();
expect(mockReturnsService.processReadySubscriptionAnnouced$Spy).toHaveBeenCalledTimes(1);
}));
Service:
private activateProcessReadySubscriptionSource = new Subject<number>();
processReadySubscriptionAnnouced$ = this.activateProcessReadySubscriptionSource.asObservable();
announceProcessReady(itemsInCart: number) {
this.activateProcessReadySubscriptionSource.next(this.returnCartDataLength);
}
I can't seem to figure out how to get the subscription to test properly.
(This is very basic stuff in the end, but it took me days to figure it out... Hope to help someone out there to save some time :)... )
I had the same problem and the only way I could fix it, was using a getter, in order to be able to return a mocked value while testing...
So in your Service, you would have to change the property to a getter:
private activateProcessReadySubscriptionSource = new Subject<number>();
processReadySubscriptionAnnouced$ () { return this.activateProcessReadySubscriptionSource.asObservable();}
After that, you would have to modify the way you access that property in order to (now) execute it on your component.
And now you can have access to the observable subscribe function on the .spec.ts file...
I'll tell you my similar history in code now:
I had:
/* * * * MyData.service.ts * * * */
// TYPING ASUMPTIONS...
// - DI = [Your defined interface]
// - BS = BehaviorSubject
@Injectable()
export class MyDataService {
private searchToggleSource: BS<DI> = new BS<DI>({ search: false });
public currentToggleSearchStatus: Observable<DI> = this.searchToggleSource.asObservable();
}
/* * * * My.component.ts * * * */
@Component({
selector: 'my-component',
template: './my.component.html',
styleUrls: ['./my.component.css'],
})
export class MyComponent implements OnInit, OnDestroy {
private searchToggleSubscription: Subscription;
public search: boolean;
// DataService being the injected, imported service
constructor(dataService: DataService){ }
ngOnInit(){
this.searchToggleSubscription = this.dataService.currentToggleSearchStatus
.subscribe(
({ search }) => {
this.search = search;
});
}
ngOnDestroy(){
this.searchToggleSubscription.unsubscribe();
}
}
/* * * * My.component.spec.ts * * * */
// ASUMPTIONS
// - USING 'jest'
describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let mockDataService;
beforeEach(() => {
mockDataService = createSpyObj('DataService', ['currentToggleSearchStatus', ]);
TestBed.configureTestingModule({
declarations: [
MyComponent,
],
providers: [
{ provide: DataService, useValue: mockDataService },
]
});
fixture = TestBed.createComponent(MyComponent);
});
it('should get state from DataService when ngOnInit', () => {
mockDataService
.currentToggleSearchStatus
.mockReturnValue(of({search: true}));
//... to call ngOnInit()
// ****************** THE ERROR **************************
// **** subscribe is not a function...
// **** Since I have no access to a real Observable from
// **** a fake DataService property...
fixture.detectChanges();
// *** SERIOUSLY I SPEND ALMOST 3 DAYS RESEARCHING AND PLAYING
// *** WITH THE CODE AND WAS NOT ABLE TO FIND/CREATE A SOLUTION...
// *** 'TILL LIGHT CAME IN...
// *******************************************************
expect(fixture.componentInstance.search).toBe(false)
});
});
The solution... use a getter... I'll use comments '-' to show 'the fix'...
/* * * * MyData.service.ts * * * */
// TYPING ASUMPTIONS...
// - DI = [Your defined interface]
// - BS = BehaviorSubject
@Injectable()
export class MyDataService {
private searchToggleSource: BS<DI> = new BS<DI>({ search: false });
//------- CHANGED ---
// public currentToggleSearchStatus: Observable<DI> = this.searchToggleSource.asObservable();
//------- A GETTER ------ (MUST RETURN THE OBSERVABLE SUBJECT)
public currentToggleSearchStatus(){
return this.searchToggleSource.asObservable();
}
}
/* * * * My.component.ts * * * */
@Component({
selector: 'my-component',
template: './my.component.html',
styleUrls: ['./my.component.css'],
})
export class MyComponent implements OnInit, OnDestroy {
private searchToggleSubscription: Subscription;
public search: boolean;
// DataService being the injected, imported service
constructor(dataService: DataService){ }
ngOnInit(){
//------------ CHANGED -------
//this.searchToggleSubscription = this.dataService.currentToggleSearchStatus
//.subscribe(
// ({ search }) => {
// this.search = search;
// });
//------------ EXECUTE THE SERVICE GETTER -------
this.searchToggleSubscription = this.dataService.currentToggleSearchStatus()
.subscribe(
({search}) => {
this.search = search;
}
);
}
ngOnDestroy(){
this.searchToggleSubscription.unsubscribe();
}
}
/* * * * My.component.spec.ts * * * */
// ASUMPTIONS
// - USING 'jest'
describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
let mockDataService;
beforeEach(() => {
mockDataService = createSpyObj('DataSharingSearchService', ['showHideSearchBar', 'currentToggleSearchStatus', ]);
TestBed.configureTestingModule({
declarations: [
MyComponent,
],
providers: [
{ provide: DataService, useValue: mockDataService },
]
});
fixture = TestBed.createComponent(MyComponent);
});
it('should get state from DataService when ngOnInit', () => {
mockDataService
.currentToggleSearchStatus
.mockReturnValue(of({search: true}));
//... to call ngOnInit()
// ------------- NO ERROR :3!!! -------------------
fixture.detectChanges();
expect(fixture.componentInstance.search).toBe(false)
});
});
*** NOTES: jest API is pretty similar to jasmine...
// jest: jasmine:
// createSpyObj <=> jasmine.createSpyObj
// .mockReturnValue() <=> .and.returnValue()
Don't forget to import only the 'of' function, to return observable objects in your mocked services...
import { of } from "rxjs/observable/of";
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