Test Angular2 service with mock backend - http

First: I'm aware that Angular2 is in alpha and changing frequently.
I'm working with Angular2. There is an injectable service with http dependency that I'd like to test using a mock backend. The service works when the app starts but I'm having no luck writing the test and getting the mock backend to respond. Any insight, is there something obvious in the test setup or implementation that I'm missing?
service/core.ts:
import { Injectable } from 'angular2/angular2';
import { Http } from 'angular2/http';
#Injectable()
export class CoreService {
constructor(public http:Http) {}
getStatus() {
return this.http.get('/api/status')
.toRx()
.map(res => res.json());
}
}
service/core_spec.ts:
import {
AsyncTestCompleter,
TestComponentBuilder,
By,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit
} from 'angular2/test';
import { MockBackend, MockConnection, BaseRequestOptions, Http, Response } from 'angular2/http';
import { Injector, bind } from 'angular2/angular2';
import { ObservableWrapper } from 'angular2/src/core/facade/async'
import { CoreService } from 'public/services/core'
export function main() {
describe('public/services/core', () => {
let backend: MockBackend;
let response: Response;
let coreService: CoreService;
let injector: Injector;
afterEach(() => backend.verifyNoPendingRequests());
it('should get status', inject([AsyncTestCompleter], (async) => {
injector = Injector.resolveAndCreate([
BaseRequestOptions,
MockBackend,
bind(Http).toFactory((backend, options) => {
return new Http(backend, options)
}, [MockBackend, BaseRequestOptions]),
bind(CoreService).toFactory((http) => {
return new CoreService(http);
}, [Http])
]);
backend = injector.get(MockBackend);
coreService = injector.get(CoreService);
response = new Response('foo');
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
expect(c.request.url).toBe('/api/status');
c.mockRespond(response);
});
// attempt #1: fails because res argument is undefined
coreService.getStatus().subscribe(res => {
expect(res).toBe('');
async.done();
});
// attempt #2: fails because emitter.observer is not a function
ObservableWrapper.subscribe(coreService.getStatus(), res => {
expect(res).toBe('');
async.done();
});
}));
});
}
Related:
https://github.com/angular/angular/issues/3502
https://github.com/angular/angular/issues/3530

I just found this topic while looking for testing tips but I can't see a direct answer to that so...
This one is based on Angular RC.1
Testing service with Mock Backend
Let's say your service is:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
#Injectable()
export class CoreService {
constructor(private http: Http) {}
getStatus() {
return this.http.get('/api/status');
}
}
Test to the service above will look like this:
import {
beforeEach,
beforeEachProviders,
describe,
expect,
inject,
it,
} from '#angular/core/testing';
import { provide } from '#angular/core';
import { BaseRequestOptions, Response, ResponseOptions } from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
describe('Http', () => {
beforeEachProviders(() => [
CoreService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]);
beforeEach(inject([MockBackend], (backend: MockBackend) => {
const baseResponse = new Response(new ResponseOptions({ body: 'status' }));
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
}));
it('should return response when subscribed to getStatus',
inject([CoreService], (coreService: CoreService) => {
coreService.getStatus().subscribe((res: Response) => {
expect(res.text()).toBe('status');
});
})
);
})
What you really have to look at there is to have proper mocking in beforeEachProviders. Test itself is quite simple and ends up with subscribing to the service method.
Note: Don't forget to set base providers first:
import { setBaseTestProviders } from '#angular/core/testing';
import {
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS,
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
} from '#angular/platform-browser-dynamic/testing';
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);

Since asking this question we did upgrade to Angular2 RC 1. Our imports look like Wojciech Kwiatek's (thank you for your answer!) but our testing strategy is slightly different. We wanted to assert on the request as well as the response. Instead of using beforeEachProviders(), we used beforeEach() where we create our own injector and save a reference to the service-under-test and mock backend. This allows us to assert on the request and manage the response inside the test, and it lets us use the verifyNoPendingRequests() method after each test.
describe('core-service', () => {
let service: CoreService;
let backend: MockBackend;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate(<any> [
CoreService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
})
]);
service = <CoreService> injector.get(CoreService);
backend = <MockBackend> injector.get(MockBackend);
});
afterEach(() => backend.verifyNoPendingRequests());
it('should get status', () => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual('api/status');
c.mockRespond(new Response(new ResponseOptions({ body: 'all is well' })));
});
service.getStatus().subscribe((status) => {
expect(status).toEqual('all is well');
});
}));
});
Edit: Plunker updated to RC2.
https://plnkr.co/edit/nlvUZVhKEr8d2mz8KQah?p=preview

Related

anguar2 nativescript error "TypeError: undefined is not an object (evaluating 'this.router')"

I am new to Angular2 Native script Programming... i need to navigate one page to another. i am stuck with the typeError:"undefined is not an object (evaluating 'this.router')" plz help me out..
my code is given..
//page1.ts
public constructor(private router: Router) { }
getMyDrawing(args) {
let pad = this.DrawingPad.nativeElement;
let img: Image = this.signImage.nativeElement;
let drawingImage;
pad.getDrawing().then
(
(data) => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
);
}
//routing.ts
import { DrawingPadExample } from "./app.component";
import { Page2Component } from "./app.page2";
export const routes = [
{ path: "drawing-pad-example", component: DrawingPadExample},
{ path: "page2", component: Page2Component }
];
export const navigatableComponents = [
DrawingPadExample,
Page2Component
];
//module.ts
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { NativeScriptModule } from "nativescript-
angular/nativescript.module";
import { DrawingPadExample } from "./app.component";
import { routes, navigatableComponents } from "./app.routing";
import { NativeScriptRouterModule } from "nativescript-
angular/router";
#NgModule({
bootstrap: [
DrawingPadExample
],
imports: [
NativeScriptModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes)
],
declarations: [
DrawingPadExample,
...navigatableComponents
],
schemas: [
NO_ERRORS_SCHEMA
]
})
export class AppModule { }
When you say navigation is not working, do you nothing at all happens and no errors are generated?
I would advice you try putting the callback within a zoneCallback.
public constructor(private ngZone: NgZone, private router: Router){
}
public getMyDrawing(args) {
let pad = this.DrawingPad.nativeElement;
let img: Image = this.signImage.nativeElement;
let drawingImage;
pad.getDrawing().then((data) => {
this.ngZone.run(() => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
});
});
}
Add instance in constructor for router.
constructor(private router : Router){}
because as error stated typeError:"undefined is not an object (evaluating 'this.router')" you are trying to access
this.router which is not initilized yet
#JBR, No, I mean dependency injection using constructor, for example,
constructor(private router: Router) {}
add that line to you page1 component.
Update
replace this
function(data) {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
with arrow function
(data) => {
console.log(data);
drawingImage = data;
img.src=data;
this.router.navigate(['page2']);
}
I got this bug when I was passing a function between two components.
In the first component, I imported Router, but in the second I didn't. Make sure that you import all necessary libraries when you are using callback functions.

Extend http on dont call request method in Angular 2

I new in Angular 2 and i'm trying create an App with JWT. So, to do this I follow the post http://www.adonespitogo.com/articles/angular-2-extending-http-provider/.
But i'm a issue, the request method is never call, after login i have to refresh the page to send the token.
Here my classes
http.service.ts
import { Injectable } from '#angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class HttpService extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = localStorage.getItem('auth_token'); // your custom token getter function here
options.headers.set('Authorization', `Bearer ${token}`);
options.headers.append('Content-Type', 'application/json');
super(backend, options);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem('auth_token');
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
// let's make option object
options = {headers: new Headers()};
}
options.headers.set('Authorization', `Bearer ${token}`);
options.headers.append('Content-Type', 'application/json');
} else {
// we have to add the token to the url object
url.headers.set('Authorization', `Bearer ${token}`);
url.headers.append('Content-Type', 'application/json');
}
return super.request(url, options)
.catch(this.catchAuthError(this));
}
private catchAuthError (self: HttpService) {
// we have to pass HttpService's own instance here as `self`
return (res: Response) => {
console.log(res);
if (res.status === 401 || res.status === 403) {
// if not authenticated
console.log(res);
}
return Observable.throw(res);
};
}
}
app.module.ts
providers: [{
provide: HttpService,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new HttpService(backend, options);
},
deps: [XHRBackend, RequestOptions]
}, LoggedInGuard, UserService],
picture.service.ts
#Injectable()
export class PictureService {
url: string = 'v1/pictures';
constructor(private http: HttpService) { }
list(): Observable<PictureComponent[]> {
return this.http
.get(this.url)
.map(res => res.json());
}
}
Component to consume picture.service.ts
#Component({
moduleId: module.id,
selector: 'picture-list',
templateUrl: './pictureList.component.html'
})
export class ListagemComponent {
pictures: PictureComponent[] = [];
service: PictureService;
msg: String = '';
constructor(service: PictureService){
this.service = service;
this.service
.list()
.subscribe(pictures => {
this.pictures = pictures;
}, err => console.log(err));
}
}
thanks for help
Did you provide picture.service in your module? If not, you should provide it in your #component or in your module if you want it globally.
I extend XHRBackend
import { Injectable } from '#angular/core';
import { Request, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
#Injectable()
export class ExtendedXHRBackend extends XHRBackend {
constructor(browserXhr: BrowserXhr, baseResponseOptions: ResponseOptions, xsrfStrategy: XSRFStrategy) {
super(browserXhr, baseResponseOptions, xsrfStrategy);
}
createConnection(request: Request) {
let token = localStorage.getItem('token');
request.headers.set('x-access-token', `${token}`);
request.headers.set('Content-Type', 'application/json');
let xhrConnection = super.createConnection(request);
xhrConnection.response = xhrConnection.response.catch((error: Response) => {
if (error.status === 401 || error.status === 403) {
console.log('access not alowed');
localStorage.removeItem('token');
}
return Observable.throw(error);
});
return xhrConnection;
}
}
and use on app module
providers: [{ provide: XHRBackend, useClass: ExtendedXHRBackend }]
After this I resolve the issue and works preety well

Angular2 - unable to retrieve data from API

I am currently building an Angular2 application accessing an MVC web API i have built. However, it does not seem to retrieve any data. I am obviously missing something but i am not sure what.
I know that the URL i am using works along with the headers as i am able to retrieve the data correctly through fiddler.
My repack.service.ts is as follows:
import { Injectable } from '#angular/core';
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { RepackIndex } from './RepackIndex';
#Injectable()
export class RepackService{
private baseUrl = 'https://localhost:44321/api/Repack/All';
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http) { }
getAllRepacks(): Promise<RepackIndex[]>{
var data = this.http.get(this.baseUrl)
.toPromise()
.then(response => response.json().data as RepackIndex[])
.catch(this.handleError);
return data;
}
private handleError(error: any): Promise<any>{
console.error("An error occured in repack.service", error);
return Promise.reject(error.message || error);
}
}
And this is my component:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { RepackIndex } from './repackIndex';
import { RepackService } from './repack.service';
#Component({
selector: 'index',
templateUrl: 'app/index.component.html',
providers: [RepackService]
})
export class IndexComponent implements OnInit{
repacks: RepackIndex[];
selectedRepack: RepackIndex;
constructor(private router: Router, private repackService: RepackService) { }
onSelect(repack: RepackIndex): void{
this.selectedRepack = repack;
}
getRepacks(): void{
this.repackService.getAllRepacks().then(repacks => this.repacks = repacks);
}
ngOnInit(): void{
this.getRepacks();
}
}
I have tried putting in a breakpoint and adding a console.log line but no data is returned to the component.
I am fairly new to Angular2 so any help would be greatly appreciated.
Thanks,
Right I have managed to get it to work by using an observable rather than a promise.
My service method now looks like this:
public GetAll = (): Observable<RepackIndex[]> => {
return this.http.get(this.baseUrl)
.map((response: Response) => <RepackIndex[]>response.json())
.catch(this.handleError);
}
And my Component call now looks like this:
getRepacks(): void{
this.repackService.GetAll()
.subscribe((data:RepackIndex[]) => this.repacks = data,
error => console.log(error),
() => console.log('Get All repacks complete'));
}
I found the answer here
Hope this helps someone else

Cache data but check in the background for new data

I want to cache my data, but at the same time I need my data to be up-to-date. I found this: Angular2 easiest way to cache HTTP responses but this will not check for new data.
I have this now in my service:
public publishedSessions: Session[] = null;
getPublishedSessions(): Observable<any> {
let headers = new Headers();
headers.append('authorization', this.userService.getToken());
if (this.publishedSessions) {
this.http.get(this.apiUrl + 'api/sessions/published', {
headers: headers
})
.map(res => res.json().sessions)
.subscribe(sessions => this.publishedSessions = sessions);
return Observable.of(this.publishedSessions);
} else {
return this.http.get(this.apiUrl + 'api/sessions/published', {
headers: headers
})
.do(res => this.publishedSessions = res.json().sessions)
.map(res => res.json().sessions)
.catch((error) => Observable.of(error));
}
}
And some standard code in my component:
handlePublishedSessions(): void {
this.subscriptionArr.push(this.sessionService.getPublishedSessions().subscribe(sessions => {
this.session = sessions
}));
}
This causes the effect that when I first navigate (visit 1) to the page, a call (call 1) will be made (wanted). Then if I navigate away and return back to the page (visit 2), the data from call 1 will be returned (not wanted), in the meantime, call 2 is in the works. So if I then navigate away again and navigate back (visit 3), the data from call 2 is being returned.
I want that the call 1 data is displayed on visit 2 for the first few milliseconds (untill call 2 is done). When call 2 is done I want the data to be replaced (without user interaction).
I would use a BehaviorSubject to cache data.
Take a look at this plunker to get an idea: https://plnkr.co/edit/jNNQJToYia2MhIE488YX?p=preview
import {Component, NgModule, Injectable} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import {HttpModule, Http} from '#angular/http';
import {BehaviorSubject} from 'rxjs/Rx';
#Injectable()
export class AnyService {
public data = new BehaviorSubject<string>();
constructor(private _http: Http) { }
public getData(): string {
this._http.get('https://httpbin.org/bytes/12')
.subscribe(
resp => this.data.next(resp._body)
);
return this.data.value;
}
}
#Component({
selector: 'my-app',
template: `
<div>
<h2 (click)="getData()">Hello {{name}} -- CLICK ME !! --</h2>
</div>
`,
})
export class App {
name:string;
firstSubscribeCallback = false;
constructor(private _srvc: AnyService) {
this.name = 'Angular2'
this._srvc.data.subscribe(
newData => {
// FIRST CALL WILL BE THE CACHED DATA..
if (!this.firstSubscribeCallback) { // JUST FOR DEMO ..
console.log('got cached data # startup..');
this.firstSubscribeCallback = true;
}
else console.log('got new data:');
console.log(newData);
}
);
this.getData(); // get FRESH data ..
}
getData() {
console.log('getting cached data:');
console.log(this._srvc.getData());
}
}
#NgModule({
imports: [ BrowserModule, HttpModule ],
declarations: [ App ],
providers: [ AnyService ],
bootstrap: [ App ]
})
export class AppModule {}

Http doesn't work in Angular 2 custom asynchronous validation

I'm trying to create a custom asynchronous validator, that goes to the server and checks if an email is already registered.
Unfortunately, it seems that the get request is never fired, because nothing happens. I've tried multiple console.logs inside subscribe, but they didn't run.
I've checked if that request works outside of the validator, and it does, so that's not the problem.
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '#angular/forms';
import { Response, Http } from '#angular/http';
#Component({
templateUrl: 'build/pages/welcome/signup/signup.html',
providers: [AuthService, CustomValidators]
})
export class Signup {
signupForm: FormGroup;
constructor(private formBuilder: FormBuilder, private http: Http) {
this.signupForm = formBuilder.group({
'email': ['', Validators.required, this.checkEmail],
}):
}
checkEmail(control: FormControl): Promise<any> {
const promise = new Promise<any>(
(resolve, reject) => {
this.http.get('/sharealead/main.php?action=checkEmail').subscribe(
(res: Response) => {
console.log('it never gets here');
console.log(res)
if (res.text() == 'already there') {
resolve({'emailTaken': true});
} else {
resolve(null);
}
},
(err) => {
console.log('it never gets here');
console.log(err);
}
)
}
);
return promise;
}
}
It's because you reference the function and you lose the this context. You can use the bind method or a wrapping arrow function to fix that (link the function to the component instance):
this.signupForm = formBuilder.group({
'email': ['', Validators.required, this.checkEmail.bind(this) ],
});
or
this.signupForm = formBuilder.group({
'email': ['', Validators.required, (control:Control) => {
return this.checkEmail(control);
} ],
});
In your case, this doesn't contain an http property...

Resources