I have an Angular component, MyComponent , which calls a method returning an Observable and subscribes to it, something like
export class MyComponent {
someProperty;
constructor(service: MyService) {}
someButtonClicked() {
this.service.doStuffAsync().subscribe(
resp => this.someProperty = resp;
);
}
}
@Injectable()
export MyService {
doStuffAsync() {
// returns an Observable which notifies asychronously, e.g. like HttoClient.get(url)
}
}
I want to test the method someButtonClicked()
and therefore I create a MyServiceMock class which I inject in the test
export class MyServiceMock {
doStuffAsync() {
return of({// some object}).pipe(observeOn(asyncScheduler));
}
}
For whatever reason I want doStuffAsync()
of MyServiceMock to be asynchronous so observeOn(asyncScheduler)
is used.
At this point though I do not know how to test someButtonClicked()
. I have tried different strategies, eg the following
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
{ provide: MyService, useClass: MyServiceMock },
]
}).compileComponents();
}));
let fixture: ComponentFixture<MyComponent>;
let component: MyComponent;
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('test someButtonClicked', async(() => {
component.someButtonClicked();
fixture.whenStable().then(() => {
expect(component.someProperty).toBeDefined();
});
}));
but this fails since doStuffAsync()
of MyServiceMock asynchronous.
So my question is which is the best strategy to test the side effects (ie someProperty
being set correctly) induced by a method which subscribes to an asynchronous Observable.
// All code should be synchronous within this it block
it('test someButtonClicked', async(() => {
// sync - button clicks are synchronous
component.someButtonClicked();
// but after btn clicks, we need to let some async code to finish
// like rxjs subscriptions etc.
await fixture.whenStable();
// and then continue
expect(component.someProperty).toBeDefined();
}));
Don't use then fixture.whenStable().then()
I use a helper function called emitted
export const emitted = obs$ => new Promise(function(resolve, reject) {
const emitted$ = new Subject();
obs$.pipe(takeUntil(emitted$)).subscribe(
_ => {
emitted$.next();
emitted$.complete();
resolve(true);
},
_ => {
emitted$.next();
emitted$.complete();
reject('An error occurred');
}
)
});
If your mock services shared the same instance of the observable with the component
export class MyServiceMock {
private obs$ = of({// some object}).pipe(observeOn(asyncScheduler));
doStuffAsync() {
return obs$;
}
}
inside your spec you can await it to have emitted
it('test someButtonClicked', async(async () => {
component.someButtonClicked();
const service = TestBed.get(MyService);
await emitted(service.doStuffAsync());
expect(component.someProperty).toBeDefined();
}));
Seeing you subscribed to the same instance of the observable as the component you know the component has run it's observer function as well as you subscribed after the component did.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.