Reputation: 4360
I am trying to test an angular2 application. I have a login form, which uses an observable to send data to the backend:
doLogin() {
this.usersService.login(this.model)
.subscribe((data) => {
console.log("In observable: " + data.isSuccess);
if (!data.isSuccess) {
this.alerts.push({});
}
});
}
In tests I am adding a spy on the service function, which returns observable, so that component can work on it:
usersService.login.and.returnValue(Observable.of(
<LoginResponse>{
isSuccess: true
}));
When everything is ready, I dispatch an event on submit button, which triggers doLogin function in component:
submitButton.dispatchEvent(new Event("click"));
fixture.detectChanges();
It works correctly. Unfortunately, when I check if usersService.login has been called in the test:
expect(usersService.login).toHaveBeenCalled();
I get an error, because the observable didn't finish and login has not been called yet.
How should I make sure, I check my spy after observable has finished?
Upvotes: 2
Views: 6997
Reputation: 202156
I don't know how you configure the service on the component but it works for me when I override providers of the component created from TestComponentBuilder
.
Let's take a sample. I have a service that returns a list of string:
import {Observable} from 'rxjs/Rx';
export class MyService {
getDogs() {
return Observable.of([ 's1', 's2', ... ]);
}
}
A component uses this service to display a list asynchronously when clicking a button:
@Component({
selector: 'my-list',
providers: [MyService],
template: `
<ul><li *ngFor="#item of items">{{ item }}</li></ul>
<div id="test" (click)="test()">Test</div>
`
})
export class MyList implements OnInit {
items:Array<string>;
service:MyService;
constructor(private service:MyService) {
}
test() {
this.service.getDogs().subscribe(
(dogs) => {
this.items = dogs;
});
}
}
I want to test that when I click on the "Test" button, the test
method of the component is called and the getDogs
method of the service is indirectly called.
For this, I create a test that instantiate directly the service and load the component using TestComponentBuilder
. In this case, I need to call the overrideProviders
method on it before calling createAsync
. This way, you will be able to provide your spied service to be notified of the call. Here is a sample:
let service:MyService = new MyService();
beforeEach(() => {
spyOn(service, 'getDogs').and.returnValue(Observable.of(
['dog1', 'dog2', 'dog3']));
});
it('should test get dogs', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders(MyList, [provide(MyService, { useValue: service })])
.createAsync(MyList).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
}));
Edit
Since the event is triggered asynchronously, you could consider to use fakeAsync
. The latter allows you to completly control when asynchronous processing are handled and turn asynchronous things in to synchronous ones.
You could wrap your test processing into
fakeAsync((): void => {
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
For more details, you could have a look at this question:
Upvotes: 2