angular 2 testing - stubing a window open and flush interval - unit-testing

I need to unit test this part of my code in MyService
social(someUrl){
let intervalTimer = Observable.interval(1000);
let authWindow = window.open(someUrl);
let pingWindow = intervalTimer.subscribe(()=>{
if(authWindow.closed){
pingWindow.unsubscribe();
return this.http.get(...)
}
})
}
it pops up with some Url which calls to execute some function in my backend and once it's done, it will be redirected to an empty page which closes the pop up (so just window.close() in the script)
So basically the code above checks every second if the pop up is still open, if not it unsubscribes and returns a http.get request as Observable.
I have two major questions:
How to stub the window.open and test, that someUrl is being used, I've read that so far you can only use jasmine? In Sinon you have sinon.stub(window, 'open',()=>{})
how to test the http.get inside the interval? I'm getting this error
1 periodic timer(s) still in the queue.
this is my test.spect setup
const mockHttpProvider = {
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions)
}
};
describe('my Test', () => {
beforeEachProviders(() => {
return [
MyService,
MockBackend,
BaseRequestOptions,
provide(Http, mockHttpProvider)
]
});
it('should...', inject([MyService, MockBackend], fakeAsync((myService: MyService, backend: MockBackend) => {
backend.connections.subscribe((connection: MockConnection) => {
???
});
myService.social().subscribe(res => {
???
});
})))
thanks!

Related

Angular2 unit test service with Http call in constructor

I am struggling to unit test Angular2 service which has async Http call in the constructor (now I wonder should it be here in the first place).
Example code below - the mocked call never seems to have been executed and I am not sure where should I put it in. The test fails as the property I am asserting is undefined at the time of execution. I tried with fakeAsync and tick() but that didnt work neither.
Service class:
#Injectable
export class Service {
private data: any; //some object that will be returned from server
constructor(private http: Http) {
this.http.get('url')
.map( (res: Response) => res.json())
.subscribe(res => this.data = res);
}
getId() {
return data.id;
}
}
The unit test:
describe('service test...', () => {
let service: Service;
let backend: MockBackend;
let result = { id: 123 };
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
Service,
{
provide: Http,
useFactory: (mockBackend, options) => {
return new Http(mockBackend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions
]
});
});
beforeEach(inject([Service, MockBackend], (s, mb) => {
service = s;
backend = mb;
backend.connections.subscribe((conn) => {
conn.mockRespond(new Response(new ResponseOptions({body: result})));
});
}));
describe('test...', () => {
it('should have id of 123...', async(() => {
expect(service.getId()).toEqual(123);
}));
});
});

mockRespond method inside beforeEach invokes later than XHR to backend inside test

I am trying to write Jasmine tests using MockBackend from Angular2.
Here is the code that I write to test a service which communicate with backend:
import { inject, TestBed } from '#angular/core/testing';
import {VehicleCategoryService} from './VehicleCategoryService';
import { BaseRequestOptions, Response, ResponseOptions, Http } from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
import {HttpService} from "../../api/HttpService";
import {ApplicationService} from "../../api/ApplicationService";
describe('VehicleCategoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [VehicleCategoryService,BaseRequestOptions, MockBackend, ApplicationService, {
provide: HttpService,
useFactory: (backend, options, applicationService) => {
return new HttpService(backend, options, applicationService);
},
deps: [MockBackend, BaseRequestOptions, ApplicationService],
}],
});
});
beforeEach(inject([MockBackend], (backend: MockBackend) => {
const baseResponse = new Response(new ResponseOptions({ body: '{"Name":"MiniVan"}' }));
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
}));
it('should be defined', inject([VehicleCategoryService], (vehicleCategoryService: VehicleCategoryService) => {
expect(vehicleCategoryService).toBeDefined();
}));
it('should return response when querying vehicleCategories', inject([VehicleCategoryService], (vehicleCategoryService: VehicleCategoryService) => {
vehicleCategoryService.query().subscribe((res: Response) => {
expect(res['Name']).toBe('MiniVan');
});
}));
});
http://stackoverflow.com/questions/ask#
HttpService here is a service that extends from built-in Http Angular2 service. query() method returns Observable as a usual Http do.
Debugging issue demonstarte that mockRespond() method inside subscription invokes later than actual call to backend that is why I receive undefined response. How can I solve this problem?

Unit Test RxJS Observable.timer using typescript, karma and jasmine

Hi I'm relatively new to Angular2, Karma and Jasmine. Currently I'm using Angular 2 RC4 Jasmine 2.4.x
I have an Angular 2 service which periodically calls an http service like this:
getDataFromDb() { return Observable.timer(0, 2000).flatMap(() => {
return this.http.get(this.backendUrl)
.map(this.extractData)
.catch(this.handleError);
});
}
Now I want to test the functionality. For testing purposes I have just tested the "http.get" on a separate function without the Observable.timer by doing:
const mockHttpProvider = {
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
}
describe('data.service test suite', () => {
var dataFromDbExpected: any;
beforeEachProviders(() => {
return [
DataService,
MockBackend,
BaseRequestOptions,
provide(Http, mockHttpProvider),
];
});
it('http call to obtain data',
inject(
[DataService, MockBackend],
fakeAsync((service: DataService, backend: MockBackend) => {
backend.connections.subscribe((connection: MockConnection) => {
dataFromDbExpected = 'myData';
let mockResponseBody: any = 'myData';
let response = new ResponseOptions({ body: mockResponseBody });
connection.mockRespond(new Response(response));
});
const parsedData$ = service.getDataFromDb()
.subscribe(response => {
console.log(response);
expect(response).toEqual(dataFromDbExpected);
});
})));
});
I obviously want to test the whole function with the Observable.timer. I think one might want to use the TestScheduler from the rxjs framework, but how can I tell to only repeat the timer function for x times? I couln't find any documentation using it in the typescript context.
Edit: I'm using rxjs 5 beta 6
Edit: Added working example for Angular 2.0.0 final release:
describe('when getData', () => {
let backend: MockBackend;
let service: MyService;
let fakeData: MyData[];
let response: Response;
let scheduler: TestScheduler;
beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
backend = be;
service = new MyService(http);
fakeData = [{myfake: 'data'}];
let options = new ResponseOptions({ status: 200, body: fakeData });
response = new Response(options);
scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function (initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, scheduler);
});
}));
it('Should do myTest', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
scheduler.schedule(() => {
service.getMyData().subscribe(
myData => {
expect(myData.length).toBe(3,
'should have expected ...');
});
}, 2000, null);
scheduler.flush();
})));
});
You need to inject the TestScheduler into the timer method inside a beforeEach part:
beforeEach(function() {
this.scheduler = new TestScheduler();
this.scheduler.maxFrames = 5000; // Define the max timespan of the scheduler
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, this.scheduler);
});
});
After that you have full control of the time with scheduleAbsolute:
this.scheduler.schedule(() => {
// should have been called once
// You can put your test code here
}, 1999, null);
this.scheduler.schedule(() => {
// should have been called twice
// You can put your test code here
}, 2000, null);
this.scheduler.schedule(() => {
// should have been called three times
// You can put your test code here
}, 4000, null);
this.scheduler.flush();
You need scheduler.flush() to start the TestScheduler.
edit: so if you want to only test it X times, use the schedule functions as often (and with the right absolute times in milliseconds) as you wish.
edit2: I added the missing scheduler start
edit3: I changed it so should be working with RxJs5
edit4: Add maxFrames setting since the default is 750ms and will prevent testing longer-running sequences.
I had issues with the TestScheduler() approach because the schedule() arrow function would never execute, so I found another path.
The Observable.timer function just returns an Observable, so I created one from scratch to give me complete control.
First, create a var for the observer:
let timerObserver: Observer<any>;
Now in the beforeEach() create the spy and have it return an Observable. Inside the Observable, save your instance to the timer:
beforeEach(() => {
spyOn(Observable, 'timer').and.returnValue(Observable.create(
(observer => {
timerObserver = observer;
})
));
});
In the test, just trigger the Observable:
it('Some Test',()=>{
// do stuff if needed
// trigger the fake timer using the Observer reference
timerObserver.next('');
timerObserver.complete();
expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});
You can test Observable timers pretty easily with fakeAsync(). Here's a component that displays a countdown timer (using a momentJS duration):
timeout.component.ts
#Component({
selector: 'app-timeout-modal',
templateUrl: './timeout-modal.component.html'
})
export class TimeoutModalComponent implements OnInit {
countdownTimer: Observable<number>;
countdownSubscription: Subscription;
durationLeft = moment.duration(60000); // millis - 60 seconds
ngOnInit() {
this.countdownTimer = Observable.timer(0, 1000);
this.countdownSubscription = this.countdownTimer
.do(() => this.durationLeft.subtract(1, 's'))
.takeWhile(seconds => this.durationLeft.asSeconds() >= 0)
.subscribe(() => {
if (this.durationLeft.asSeconds() === 0) {
this.logout();
}
});
}
}
timeout.component.spec.ts
beforeEach(async(() => {
...
}));
beforeEach(() => {
fixture = TestBed.createComponent(TimeoutModalComponent);
component = fixture.componentInstance;
});
it('should show a count down', fakeAsync(() => {
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(60);
tick(1000);
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(59);
component.countdownSubscription.unsubscribe();
}));
I was struggling with this for a while also. Since apparently a lot has changed in the frameworks since this question was asked, I thought maybe someone would be helped by my solution. My project uses rxjs 5, jasmine 2.8 and angular 5.
In my component a timer was used to call a http-get function in a service every minute. My problem was that when using fakeAsync zone the (stubbed) get function was never called and I received the error: "Error: 1 periodic timer(s) still in the queue.".
The error is showing up because the timer keeps firing and isn't stopped at the end of the test. This can be resolved by adding "discardPeriodicTasks();" to the end of the test, which causes the timer to stop. Tick(); can be used to fake to passage of time untill a next call. I used a spy on my get-function in my service to see if it worked:
it(
'should call getTickets from service every .. ms as defined in refreshTime',
fakeAsync(() => {
fixture.detectChanges();
tick();
expect(getTicketsSpy).toHaveBeenCalledTimes(1);
// let 2 * refreshtime pass
tick(2 * component.refreshTime);
expect(getTicketsSpy).toHaveBeenCalledTimes(3);
discardPeriodicTasks();
})
);
The refreshTime is the parameter that I used in the timer. I hope this prevents someone from spending half a day trying to figure this out.

angular2 testing framework async testing issue

I have created angular 2 a project and a service with angular-cli and tried to test my service.
But API does not fail in async function although it should fail ;moreover, it just ignores those exceptions.
/* tslint:disable:no-unused-variable */
import {
beforeEach, beforeEachProviders, describe, xdescribe,
expect, it, xit, async, inject, injectAsync
} from '#angular/core/testing';
import { SearchService } from './search.service';
import {provide} from '#angular/core';
import {MockBackend, MockConnection} from '#angular/http/testing';
import {XHRBackend, Response, ResponseOptions, HTTP_PROVIDERS} from '#angular/http';
describe('Search Service', () => {
let searchService: SearchService;
let mockBackend: MockBackend;
beforeEachProviders(() => [
HTTP_PROVIDERS,
MockBackend,
provide(XHRBackend, { useClass: MockBackend }),
SearchService
]);
beforeEach(injectAsync([SearchService, MockBackend], (s, m) => {
searchService = s;
mockBackend = m;
}));
it('async test', () => {
setTimeout(() => {
expect(2).toBe(1);
}, 3000);
});
It just ignores those minimal test case.
Then I have read some doc and updated my code as follow.
it('async test with done', (done) => {
setTimeout(() => {
expect(1).toBe(1);
done();
}, 1000);
});
But this time, the test fails although it should pass. The error is as follow.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I change default timeout value to bigger value but it is no effect.
injectAsync will not work, use async (stopped working for me after rc2)
angular2 change log beta 16
injectAsync is now deprecated. Instead, use the async function to wrap any asynchronous tests.
You will also need to add the dependency 'node_modules/zone.js/dist/async-test.js' as a served file in your Karma or other test configuration.
Before:
it('should wait for returned promises', injectAsync([FancyService], (service) => {
return service.getAsyncValue().then((value) => { expect(value).toEqual('async value'); });
}));
it('should wait for returned promises', injectAsync([], () => {
return somePromise.then(() => { expect(true).toEqual(true); });
}));
After:
it('should wait for returned promises', async(inject([FancyService], (service) => {
service.getAsyncValue().then((value) => { expect(value).toEqual('async value'); });
})));
// Note that if there is no injection, we no longer need `inject` OR `injectAsync`.
it('should wait for returned promises', async(() => {
somePromise.then(() => { expect(true).toEqual(true); });
}));

Jasmine gives Cannot read property 'getXHR' of null for Angular 2 unit test

I have found many examples online of unit testing Angular 2 with a http call. However when I make my own test and run it Jasmine it gives me an error:
http Http .request() should accept a fully-qualified request as its only parameter
Failed: Cannot read property 'getXHR' of null
import {
afterEach,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
injectAsync,
it,
xit
} from "angular2/testing";
import {Injector, provide} from "angular2/core";
import {MockBackend, MockConnection} from "angular2/src/http/backends/mock_backend";
import {
BaseRequestOptions,
ConnectionBackend,
Request,
RequestMethod,
RequestOptions,
Response,
ResponseOptions,
URLSearchParams,
JSONP_PROVIDERS,
HTTP_PROVIDERS,
XHRBackend,
JSONPBackend,
Http,
Jsonp
} from "angular2/http";
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";
describe("http", () => {
let url = "http://foo.bar";
let http: Http;
let injector: Injector;
let backend: MockBackend;
let baseResponse;
let jsonp: Jsonp;
beforeEach(() => {
injector = Injector.resolveAndCreate([
BaseRequestOptions,
MockBackend,
provide(
Http,
{
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
}),
provide(
Jsonp,
{
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Jsonp(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]);
http = injector.get(Http);
jsonp = injector.get(Jsonp);
backend = injector.get(MockBackend);
baseResponse = new Response(new ResponseOptions({body: "base response"}));
});
afterEach(() => backend.verifyNoPendingRequests());
describe("Http", () => {
describe(".request()", () => {
it("should return an Observable",
() => { expect(http.request(url)).toBeAnInstanceOf(Observable); });
it("should accept a fully-qualified request as its only parameter",
inject([injectAsync], (async) => {
backend.connections.subscribe(c => {
expect(c.request.url).toBe("https://google.com");
c.mockRespond(new Response(new ResponseOptions({body: "Thank you"})));
async.done();
});
http.request(new Request(new RequestOptions({url: "https://google.com"})))
.subscribe((res) => {});
}));
});
});
});
Any thoughts what I am doing wrong here?
You currently (beta.1) need to set the BrowserDomAdapter before running a unit test in Angular 2.
Using karma-test.shim.js
That can be done by adding karma-test.shim.js to your project, which contains the appropriate initialization:
System.import('angular2/src/platform/browser/browser_adapter')
.then(function(browser_adapter) {
browser_adapter.BrowserDomAdapter.makeCurrent();
})
Note that this should be done for you if you are using angular-cli.
Directly in the unit-test
Or directly in the unit test, by adding an import:
import {BrowserDomAdapter} from 'angular2/src/platform/browser/browser_adapter';
And later setting the DOM Adapter
BrowserDomAdapter.makeCurrent();