HTTP post request unit testing in Angular 4 - javascript

THIS TEST IS WORKING NOW
I would like to test an HTTP request in Angular 2, but it is not working.
Here is the error message:
ERROR in .../loginservice.service.spec.ts (45,11): ';' expected.
ERROR in .../loginservice.service.spec.ts (45,12): ')' expected.
and here is the code:
It is a post request, and it working properly.
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class LoginService {
constructor(private http: Http) { }
postLoginDetails(loginDetails): Observable<Comment[]> {
const endpoint = 'http://localhost:8080/api/login';
const bodyString = loginDetails;
const headers = new Headers({ 'Content-Type': 'application/json'});
const options = new RequestOptions({headers: headers});
return this.http.post(endpoint, bodyString, options)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'server error'));
}
}
and here is the test for it:
This is the test for the post request. I used different articles to write it and perhaps that is why it is not working.
import { TestBed, inject } from '#angular/core/testing';
import {
HttpModule,
Http,
XHRBackend,
ResponseOptions,
Response,
Headers
} from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
import { LoginService } from './loginservice.service';
describe('LoginService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
LoginService,
{ provide: XHRBackend, useClass: MockBackend },
]
});
});
describe('postLoginDetails()', () => {
it('should return an Observable<Comment[]> with ok status',
inject([LoginService, XHRBackend], (LoginService, MockBackend) => {
const mockResponse = {
status: 'ok',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRlc3RBZG1pbiIsImFkbWluIjp0cnVlfQ.nhC1EDI5xLGM4yZL2VMZyvHcbcWiXM2RVS7Y8Pt0Zuk'
}
const loginDetails = {
email: 'test#example.com',
password: '1234'
};
MockBackend.connections.subscribe((connection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(mockResponse)
})));
});
LoginService.postLoginDetails(loginDetails).subscribe((mockResponse) => {
expect(mockResponse.status).toEqual('ok');
});
}));
});
});

There is mismatch } in while closing mockBackend.connections
describe('postLoginDetails()', () => {
it('should return an Observable<Comment[]>', inject([LoginService, XHRBackend], (loginService, mockBackend) => {
const mockResponse = {
status: 'ok',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRlc3RBZG1pbiIsImFkbWluIjp0cnVlfQ.nhC1EDI5xLGM4yZL2VMZyvHcbcWiXM2RVS7Y8Pt0Zuk'
};
mockBackend.connections.subscribe((connection) => {
const loginDetails = {
email: 'test#example.com',
password: '1234'
};
loginService.postLoginDetails(loginDetails).subscribe((userInfo) => {
expect(userInfo.length).toBe(2);
expect(userInfo.status).toEqual('ok');
});
}));
});
});
update:- fixed some syntax error.

Nice question.
Yes, this is definitely a false positive test...

Related

How to get status code in angular observable

I have services below that I'd like to get status code and handle if statements in it but so far I couldn't figure it out
import { Injectable } from '#angular/core';
import { EnvService } from './env.service';
import { tap } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { NativeStorage } from '#ionic-native/native-storage/ngx';
import { Plugins } from '#capacitor/core';
const { Storage } = Plugins;
#Injectable({
providedIn: 'root'
})
export class InvoicesServiceService {
token: any;
constructor(
private env: EnvService,
private http: HttpClient,
private nativeStorage: NativeStorage
) {
Storage.get({ key: 'token' }).then((token: any) => {
this.token = JSON.parse(token.value)
}).catch(error => console.error(error));
}
// All
getInvoices(): Observable<any> {
const tokenPromise =
this.token === undefined
? Storage.get({ key: 'token' })
: Promise.resolve(this.token);
return from(tokenPromise).pipe(
switchMap((token) => {
this.token = this.token;
const httpOptions = {
headers: new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token.access_token,
}),
};
return this.http
.get(`${this.env.Dashboard}` + '/invoices', httpOptions)
.pipe(map((data) => data));
})
);
}
What I try to do is that if, status code is 403 redirect user to specific route other than that just return data.
any idea?
In component where you subscribe this service you can handle error
this.service
.getInvoices()
.subscribe((response) => {
// This is success
},
(error: HttpErrorResponse) => {
// Handle error
// Use if conditions to check error code, this depends on your api, how it sends error messages
});
Another way to handle in service itself.
return this.http
.get(`${this.env.Dashboard}` + '/invoices', httpOptions)
.pipe(map((data) => data))
.toPromise()
.then((response) => {
//Success
})
.catch((error: HttpErrorResponse) => {
// Handle error
});
Hope this helps.
The error is not always sent in the headers.
Sometimes the erros comes via HTML message, like when NGINX tells you someting before you even get to the backend:
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>
In these cases you should use if (error.includes('413 Request Entity Too Large')) {...}

Expected one matching request for criteria

I am trying to follow the angular guide to testing services using the new HTTP Client. I am getting the following error, Expected one matching request for criteria "Match method: GET, URL: http://localhost:8080/services/shift/2016-12-01", found none. I have put my code below, not too sure where I'm going wrong
Unit Test
import { HttpTestingController, HttpClientTestingModule } from '#angular/common/http/testing';
import { HttpClient, HttpHandler } from '#angular/common/http';
import { TestBed } from '#angular/core/testing';
import { ShiftService } from './shift.service';
let service: ShiftService;
let backend: HttpTestingController;
describe('ShiftService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ShiftService,
HttpClient,
HttpHandler
],
imports: [HttpClientTestingModule]
});
service = TestBed.get(ShiftService);
backend = TestBed.get(HttpTestingController);
});
afterEach(() => {
backend.verify();
});
describe('When the getShift method is invoked', () => {
it('should make a GET request to the services/shift endpoint', async() => {
service.getShift().subscribe();
backend.expectOne({
url: 'http://localhost:8080/services/shift/2016-12-01',
method: 'GET'
});
});
});
});
Service
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ShiftService {
constructor(private http: HttpClient) { }
public getShift = () => {
return this.http.get('http://localhost:8080/services/shift/2016-12-01');
}
}
I have made sure to subscribe to my getShift() method and I am using the HTTPTestingController. I have also tried the other overloads of the HttpTestingController and no luck :/ Thank you for any help in advance!
use describe as below
describe('When the getShift method is invoked', () => {
it('should make a GET request to the services/shift endpoint', async() => {
const path = '/testPath';
service.get(path).subscribe(response => {
expect(response).toBeTruthy();
});
const httpRequest = httpMock.expectOne(
(req: HttpRequest<any>) => req.urlWithParams === path);
// write your expect criteria here....
});
});

mock testing http - angular2

I have read many questions here and a few tutorials and I still can't get this working, I've copied the tutorials word-for-word and still no luck so I have no idea what the issue is. I'm pretty new to this stuff. Here's my code:
import { TestBed, inject, async } from '#angular/core/testing';
import { MockBackend } from '#angular/http/testing';
import {
BaseRequestOptions,
HttpModule,
Http,
Response,
ResponseOptions
} from '#angular/http';
import { ItemsService } from './items.service';
import { MOCKITEMS } from './mock-items';
describe('ItemsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [ItemsService]
});
});
it('should construct', inject([ItemsService], (service) => {
expect(service).toBeDefined();
}));
});
describe('ItemsService Mock', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ItemsService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend, opts) => new Http(backend, opts),
deps: [MockBackend, BaseRequestOptions]
}
],
imports: [HttpModule]
});
});
it('should construct', inject([ItemsService, MockBackend], (service, mockBackend) => {
expect(service).toBeDefined();
}));
describe('#getItems()', () => {
it('should return <Array<Items>>', inject([ItemsService, MockBackend], (service, mockBackend) => {
const mockReponse = {
data: [
{ itemCharId: 1, itemName: 'milk' },
{ itemCharId: 2, itemName: 'eggs' },
{ itemCharId: 3, itemName: 'meat' }
]
}
mockBackend.connections.subscribe((connection) => {
connection.mockRespond(new Response(new ResponseOptions({ body: JSON.stringify(mockReponse) })));
});
service.getItems().subscribe((items) => {
expect(items.length).toBe(3);
expect(items[0].itemName).toEqual('milk');
expect(items[1].itemName).toEqual('eggs');
expect(items[2].itemName).toEqual('meat');
});
}));
});
});
The tests are failing with expected undefined to be 3, etc. So I'm assuming it's not actually getting my mockResonse obj as the response or something on those lines? Could just be something small too I guess.
Service code:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
#Injectable()
export class ItemsService {
constructor(private http: Http) { }
getItems() {
return this.http.get('/api/items');
}
}
Any help greatly appreciated.
In the tutorial that was likely was followed, service method returns response.json().data:
getHeroes(): Promise<String[]> {
return this.http.get('myservices.de/api/heroes')
.toPromise()
.then(response => response.json().data)
.catch(e => this.handleError(e));
}
So the response is mocked as { data: [...] }.
While in the example in the question it returns this.http.get('/api/items') and doesn't call response.json().
These are the reason why there are no errors except failed assertions; items has to be equal to mockReponse.
It should be
getItems() {
return this.http.get('/api/items').map(res => res.json());
}
and
const mockReponse = [
{ itemCharId: 1, itemName: 'milk' },
{ itemCharId: 2, itemName: 'eggs' },
{ itemCharId: 3, itemName: 'meat' }
]
This can additionally be asserted with
expect(items).toEqual(mockResponse);

Angular2: oauth2 with token headers

I'm new to angular2. In 1.* everything was fine with interceptors, just add them: and you have everywhere your headers, and you can handle your requests, when token became invalid...
In angular2 i'm using RxJs.
So i get my token:
getToken(login: string, pwd: string): Observable<boolean> {
let bodyParams = {
grant_type: 'password',
client_id: 'admin',
scope: AppConst.CLIENT_SCOPE,
username: login,
password: pwd
};
let params = new URLSearchParams();
for (let key in bodyParams) {
params.set(key, bodyParams[key])
}
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
let options = new RequestOptions({headers: headers});
return this.http.post(AppConst.IDENTITY_BASE_URI + '/connect/token', params.toString(), options)
.map((response: Response) => {
let data = response.json();
if (data) {
this.data = data;
localStorage.setItem('auth', JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
}));
return true;
} else {
return false;
}
});
}
and then how can i use this token in every request? i don't want to set .header in every request. It's a bad practice.
And then: for example when i do any request, and get 401-error, how can i intercept, and get a new token, and then resume all requests, like it was in angular 1?
i tried to use JWT from here jwt, but it doesn't meet my requirements, btw in first angular i was using Restangular - and everything was fine there (also with manual on tokens:https://github.com/mgonto/restangular#seterrorinterceptor)
You can either extend the default http service and use the extended version, or you could create a method that gets some parameters (if necessary) and return a RequestOptions objects to pass default http service.
Option 1
You can create a service:
#Injectable()
export class HttpUtils {
constructor(private _cookieService: CookieService) { }
public optionsWithAuth(method: RequestMethod, searchParams?: URLSearchParams): RequestOptionsArgs {
let headers = new Headers();
let token = 'fancyToken';
if (token) {
headers.append('Auth', token);
}
return this.options(method, searchParams, headers);
}
public options(method: RequestMethod, searchParams?: URLSearchParams, header?: Headers): RequestOptionsArgs {
let headers = header || new Headers();
if (!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
let options = new RequestOptions({headers: headers});
if (method === RequestMethod.Get || method === RequestMethod.Delete) {
options.body = '';
}
if (searchParams) {
options.params = searchParams;
}
return options;
}
public handleError(error: Response) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
Usage example:
this._http
.get('/api/customers', this._httpUtils.optionsWithAuth(RequestMethod.Get))
.map(res => <Customer[]>res.json())
.catch(err => this._httpUtils.handleError(err));
This example is using cookies to store and access the token. You could use a parameter as well.
Option 2
Second option is to extend http service, for example like this:
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 MyHttp extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = 'fancyToken';
options.headers.set('Auth', token);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = 'fancyToken';
if (typeof url === 'string') {
if (!options) {
options = {headers: new Headers()};
}
options.headers.append('Auth', token);
} else {
url.headers.append('Auth', token);
}
return super.request(url, options).catch(this.handleError(this));
}
private handleError (self: MyHttp) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
And in your #NgModule:
#NgModule({
// other stuff ...
providers: [
{
provide: MyHttp,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new MyHttp(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
]
// a little bit more other stuff ...
})
Usage:
#Injectable()
class CustomerService {
constructor(private _http: MyHttp) {
}
query(): Observable<Customer[]> {
return this._http
.get('/api/customers')
.map(res => <Customer[]>res.json())
.catch(err => console.log('error', err));
}
}
Extra:
If you want to use refresh token to obtain a new token you can do something like this:
private handleError (self: MyHttp, url?: string|Request, options?: RequestOptionsArgs) {
return (res: Response) => {
if (res.status === 401 || res.status === 403) {
let refreshToken:string = 'fancyRefreshToken';
let body:any = JSON.stringify({refreshToken: refreshToken});
return super.post('/api/token/refresh', body)
.map(res => {
// set new token
})
.catch(err => Observable.throw(err))
.subscribe(res => this.request(url, options), err => Observable.throw(err));
}
return Observable.throw(res);
};
}
To be honest, I haven't tested this, but it could provide you at least a starting point.
We solved the issue with extension of AuthHttp. We added a method a on AuthHttp to set a new header dynamically like that (X-RoleId is a custom header)
declare module 'angular2-jwt' {
interface AuthHttp {
setRoleId(config: {});
}
}
AuthHttp.prototype.setRoleId = function (roleId) {
let jsThis = <any>(this);
jsThis.config.globalHeaders = [
{'Content-Type': 'application/json'},
{'X-RoleId': roleId}
];
};

How to do a unit test for Http get MockBackend in Angular2?

How to do a unit test for Http get MockBackend in Angular2?
I'm having trouble testing my http unit test.
Every time I look at MockBackend it seems confusing, a lot of code and some imports never work.
I just want a very basic http get unit test
I'm using: typescript, angular2, jasmine and karma runner.
My actual code works fine.
Here is my code that I'm testing:
import {Injectable} from 'angular2/angular2';
import {HTTP_PROVIDERS, Http, Headers} from 'angular2/http';
#Injectable()
export class FirebaseService{
headers: Headers;
//Test issue seems to be here when I inject Http instance.
constructor(public http?: Http) {
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
}
//This is the method I'm testing.
public getSpotifyTracks = ():Promise<Object> =>{
return this.http
.get('https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem', {headers:this.headers})
.map((response) => {
return response.json()
}).toPromise();
}
}
Here is my unit test for that code:
import {it, iit, describe, expect, inject, injectAsync, beforeEachProviders, fakeAsync, tick} from 'angular2/testing';
import {HTTP_PROVIDERS, Http, Headers} from 'angular2/http';
import {FirebaseService} from '../app/firebase-service';
describe('Firebase Service Calls', () => {
beforeEachProviders(()=> [Http, FirebaseService]);
//Issue seems to be here????
it('get all tracks from spotify', injectAsync([FirebaseService],(service) => {
return service.getSpotifyTracks().then((response) => {
expect(response.length).not.toBe(null);
});
}), 3000);
});
First import all modules :
import {it,describe,expect,inject,injectAsync,beforeEachProviders} from 'angular2/testing';
import {provide, Injector} from 'angular2/core';
import {MockBackend} from 'angular2/http/testing';
import {YourServiceToBeTested} from 'path/to/YourServiceToBeTested';
Next you need to declare the Mocked HttpBackend :
describe('Service with Http injected', () => {
beforeEachProviders(() => {
[
MockBackend,
BaseRequestOptions,
provide(
Http,
{
useFactory: (backend, defaultOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
}),
YourServiceToBeTested
]
});
Finally on each test, you need to inject the mock & set the mocked value (ie the fake data returned by your service for this specific test)
it('should respect your expectation',
inject(
[YourServiceToBeTested, MockBackend],
(yourServiceToBeTested, mockBackend) => {
let response = 'Expected Response from HTTP service usually JSON format';
let responseOptions = new ResponseOptions({body: response});
mock.connections.subscribe(
c => c.mockRespond(new Response(responseOptions)));
var res = yourServiceToBeTested.ServiceMethodToBeTest(serviceParams);
expect(res).toEqual('your own expectation');
}));
While #f-del s answer gets the same result this is easier and uses Angulars DI better.
describe('Firebase Service Calls', () => {
beforeEachProviders(()=> [
HTTP_PROVIDERS,
MockBackend,
provide(XHRBackend, {useExisting: MockBackend})]);
This way, when Http is requested, and instance that uses MockBackend is provided.
In Angular 2.2.1 provide does not exist in core anymore , so we should do :
{
provide : Http,
deps : [ MockBackend, BaseRequestOptions ],
useFactory : ( backend : MockBackend, defaultOptions : BaseRequestOptions ) => {
return new Http( backend, defaultOptions );
}
}
To piggyback off of #Milad's response, I found a great tutorial on mocking http calls for Angular 2/4 unit tests.
searchService.service.ts
import {Injectable} from '#angular/core';
import {Jsonp} from '#angular/http';
import 'rxjs/add/operator/toPromise';
class SearchItem {
constructor(public name: string,
public artist: string,
public thumbnail: string,
public artistId: string) {
}
}
#Injectable()
export class SearchService {
apiRoot: string = 'https://itunes.apple.com/search';
results: SearchItem[];
constructor(private jsonp: Jsonp) {
this.results = [];
}
search(term: string) {
return new Promise((resolve, reject) => {
this.results = [];
let apiURL = `${this.apiRoot}?term=${term}&media=music&limit=20&callback=JSONP_CALLBACK`;
this.jsonp.request(apiURL)
.toPromise()
.then(
res => { // Success
this.results = res.json().results.map(item => {
console.log(item);
return new SearchItem(
item.trackName,
item.artistName,
item.artworkUrl60,
item.artistId
);
});
resolve(this.results);
},
msg => { // Error
reject(msg);
}
);
});
}
}
searchService.service.spec.ts
describe('Service: Search', () => {
let service: SearchService;
let backend: MockBackend;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [JsonpModule],
providers: [
SearchService,
MockBackend,
BaseRequestOptions,
{
provide: Jsonp,
useFactory: (backend, options) => new Jsonp(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]
});
backend = TestBed.get(MockBackend);
service = TestBed.get(SearchService);
});
});
it('search should return SearchItems', fakeAsync(() => {
let response = {
"resultCount": 1,
"results": [
{
"artistId": 78500,
"artistName": "U2",
"trackName": "Beautiful Day",
"artworkUrl60": "image.jpg",
}]
};
// When the request subscribes for results on a connection, return a fake response
backend.connections.subscribe(connection => {
connection.mockRespond(new Response(<ResponseOptions>{
body: JSON.stringify(response)
}));
});
// Perform a request and make sure we get the response we expect
service.search("U2");
tick();
expect(service.results.length).toBe(1);
expect(service.results[0].artist).toBe("U2");
expect(service.results[0].name).toBe("Beautiful Day");
expect(service.results[0].thumbnail).toBe("image.jpg");
expect(service.results[0].artistId).toBe(78500);
}));
Code and credit goes to Asim at CodeCraft.

Categories

Resources