Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Jasmine test not firing subscribe inside ngOnInit

I have a basic component class, inside ngOnInit it subscribes to a service call

ngOnInit() {
    this.activityService.getActivities().subscribe(activities => {
        console.log('inside sub');
        this.recentActivities = activities;
    });
}

So I included this console.log to see if this subscribe ever executes.

In my test I spyOn this activity service method to return an Observable collection of the data I need. Then in the test, I do the whole fixture.detectChanges() deal and expect my array to be of length 1.

fit('should populate recent activities', () => {
    const activities = [new ActivityBuilder()
        .withId('1').withRecentActivityActionId(RecentActivityActionType.Created).build()
    ];
    spyOn(activityService, 'getActivities').and.returnValue(of(activities));

    fixture.detectChanges();
    fixture.whenStable().then(() => {
        fixture.detectChanges();
    });
    fixture.detectChanges();

    expect(component.recentActivities.length).toBe(1);
});

According to the Angular docs, they pretty much do the same thing, I even tried the fakeAsync approach, but to no avail. What am I missing here? Why does the subscribe block not execute?

like image 835
pearcebdev Avatar asked Nov 13 '18 23:11

pearcebdev


1 Answers

After hours of trying fakeAsync, providing mock class for the service, various tick() calls, and dozens of other posts and slighty different approaches, the culprit came down to adding one line: component.ngOnInit();.

Apparently, while fixture.detectChanges() certainly calls ngOnInit, it was somehow keeping the observable cold. When I would call fixture.detectChanges(), it would not go into the subscribe of the observable. As soon as I added component.ngOnInit(), the observable became hot, and the code inside the subscribe was executed, and I could see my console logs being printed. Oddly, this made the following fixture.detectChanges() calls also jump into the subscribe code for each subsequent call.

Here is a simplified version of my spec file, hopefully this aids anyone in the future with the same problem. fakeAsync and sync zones still passed the test, I had assumed I would have needed at least a tick() inside the fakeAsync zone, but it wasn't necessary.

describe('RecentActivityComponent', () => {
let component: RecentActivityComponent;
let fixture: ComponentFixture<RecentActivityComponent>;
let activityService: ActivityService;

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports: [AccordionModule, FontAwesomeModule, HttpClientTestingModule],
        declarations: [ RecentActivityComponent ],
        providers: [AccordionConfig, ActivityService]
    }).compileComponents();
}));

beforeEach(async () => {
    fixture = TestBed.createComponent(RecentActivityComponent);
    component = fixture.componentInstance;
    activityService = TestBed.get(ActivityService);
});

it('should populate recent activities', fakeAsync( () => {
    const activities = [new ActivityBuilder()
        .withId('1').withRecentActivityActionId(RecentActivityActionType.Created).build()
    ];
    spyOn(activityService, 'getActivities').and.returnValue(of(activities));
    component.ngOnInit();
    expect(component.recentActivities.length).toBe(1);
}));

it('should populate recent activities', () => {
    const activities = [new ActivityBuilder()
        .withId('1').withRecentActivityActionId(RecentActivityActionType.Created).build()
    ];
    spyOn(activityService, 'getActivities').and.returnValue(of(activities));
    component.ngOnInit();
    expect(component.recentActivities.length).toBe(1);
});

});

like image 178
pearcebdev Avatar answered Oct 17 '22 12:10

pearcebdev