How do I test a JSONP HTTP request in Angular? - javascript

I'm using the HttpClientModule and HttpClientJsonpModule to make a JSONP HTTP request in a service.
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpClientModule, HttpClientJsonpModule } from '#angular/common/http';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
HttpClientJsonpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
This service uses the jsonp method from the HttpClient class to get a JSONP response from the specified URL. I think that this response is intercepted by JsonpInterceptor and sent to the JsonpClientBackend where the request is handled.
example.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class ExampleService {
url = "https://archive.org/index.php?output=json&callback=callback";
constructor(private http: HttpClient) { }
getData() {
return this.http.jsonp(this.url, 'callback');
}
}
Using the HttpClientTestingModule, I inject the HttpTestingController so I can mock and flush my JSONP HTTP request.
example.service.spec.ts
import { TestBed, inject } from '#angular/core/testing';
import {
HttpClientTestingModule,
HttpTestingController
} from '#angular/common/http/testing';
import { ExampleService } from './example.service';
describe('ExampleService', () => {
let service: ExampleService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ExampleService]
});
service = TestBed.get(ExampleService);
httpMock = TestBed.get(HttpTestingController);
});
describe('#getData', () => {
it('should return an Observable<any>', () => {
const dummyData = { id: 1 };
service.getData().subscribe(data => {
expect(data).toEqual(dummyData);
});
const req = httpMock.expectOne(service.url); // Error
expect(req.request.method).toBe('JSONP');
req.flush(dummyData);
});
});
});
In the end, I get the error
Error: Expected one matching request for criteria "Match URL: https://archive.org/index.php?output=json&callback=callback", found none.
If I change the request method to GET, this test works as expected.
From what I can tell, the HttpClientTestingModule uses the HttpClientTestingBackend but there is no JsonpClientTestingBackend or corresponding interceptor.
How do I test a JSONP HTTP request in Angular?

According to an Angular developer this is a bug. Here's a workaround for now.
example.service.spec.ts
import { TestBed, inject } from '#angular/core/testing';
// Import the HttpClientJsonpModule, HttpBackend, and JsonpClientBackend
import { HttpClientJsonpModule, HttpBackend, JsonpClientBackend } from '#angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { ExampleService } from './example.service';
describe('ExampleService', () => {
let service: ExampleService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
// Use the HttpBackend instead of the JsonpClientBackend
providers: [ExampleService, { provide: JsonpClientBackend, useExisting: HttpBackend }]
});
service = TestBed.get(ExampleService);
httpMock = TestBed.get(HttpTestingController);
});
describe('#getData', () => {
it('should return an Observable<any>', () => {
const dummyData = { id: 1 };
service.getData().subscribe(data => {
expect(data).toEqual(dummyData);
});
// Pass a function to the expectOne method
const req = httpMock.expectOne(request => request.url === service.url);
expect(req.request.method).toBe('JSONP');
req.flush(dummyData);
});
});
});

Related

Getting error 'Expected undefined to be true' in Jasmine

Writing a test case for service function which should return true value.But i am getting error "Expected undefined to be true".below is the code for the same.
This is landingpage.serviice.spec.ts
import {Card} from "../../../shared/components/card/card.model";
import {} from 'jasmine';
import {LandingPageService} from "../../../features/admin/landing-page/landing-page.service";
import {ComponentFixture, TestBed} from "#angular/core/testing";
import {GBHttpClient, GBHttpClientCreator} from './../../../blocks/global/http-client';
import {HttpClient} from '#angular/common/http';
import {Principal,TokenService} from "../../../shared/auth";
import {Router} from '#angular/router';
import {MockRouter} from "../../../test-helpers/mock-route.service";
import {NgbModule} from "#ng-bootstrap/ng-bootstrap";
import {NgXTestModule} from "../../../test.module";
import {HttpClientTestingModule, HttpTestingController} from '#angular/common/http/testing';
import {MockBridgeService} from "../../../test-helpers/mock-bridge.service";
import {MockPrincipalService} from "../../../test-helpers/mock-principal.service";
import {TranslateService} from "#ngx-translate/core";
import {QuickTranslateService} from "../../../shared/services/quick-translate.service";
import {ParamsService} from './../../../shared/services/params.service';
import {SearchService} from "../../../features/search";
import {SpinnerService} from './../../../shared/spinner/spinner.service';
import {Observable,of} from 'rxjs';
describe('LandingPageService', () => {
// let result : boolean;
let landingPageService = {
checkLimitOrderFeatureEnabled: jasmine.createSpy(),
// checkItemCategoryLimit:jasmine.createSpy().and.returnValue(of(null))
}
let httpMock: HttpTestingController;
beforeEach(async() => {
const mockPrincipal = new MockPrincipalService();
const mockRouter = new MockRouter();
const mockBridgeService = new MockBridgeService();
const searchService = new SearchService(null, null);
await TestBed.configureTestingModule({
imports: [
NgXTestModule,
HttpClientTestingModule,
NgbModule.forRoot()
],
providers: [
{
provide: Principal,
useValue: mockPrincipal
},
{
provide: Router,
useValue: mockRouter
},
{
provide: "SearchService",
useValue: searchService
},
{
provide: "BridgeService",
useValue: mockBridgeService
},
LandingPageService,
TokenService,
QuickTranslateService,
TranslateService,
ParamsService,
SpinnerService,
{
provide: GBHttpClient,
useFactory: GBHttpClientCreator,
deps: [HttpClient]
}
]
})
landingPageService = TestBed.get(LandingPageService);
// result = false;
httpMock = TestBed.get(HttpTestingController);
});
afterEach(() => {
TestBed.resetTestingModule();
httpMock.verify();
});
it('should get Limit Order feature toggle value', () => {
spyOn(landingPageService,'checkLimitOrderFeatureEnabled').and.callThrough();
expect(landingPageService).toBeTruthy();
let result = landingPageService.checkLimitOrderFeatureEnabled();
expect(result).toBe(true);
});
});
This is landing-page.service.ts
checkLimitOrderFeatureEnabled() {
return this.principal.getUserContext().enableLimitOrder && this.principal.getUserContext().enableLimitOrderParameter;
}
For this function which is written in landingpage service i need to write test case.

How to load data service done first when app starting in Angular 8

This is my Angular App. My app will get data from API (temporarily in JSON file) and show in many another sibling component. So I decide to create a category.service.ts that I get and store data in. I using APP_INITIALIZER to run this service first when my app started. But there is a problem that: This service is running first, AppComponent runs before service get data done. So my view have empty of data.
If I click button routing to this component, everything run perfect. But when I go to this component by url path or F5(refresh page), nothing is shown
category.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class CategoryService {
DATA_CATEGORIES = 'assets/categories.json';
private _categories = [];
constructor(private http: HttpClient) {
}
get categories() {
return this._categories;
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
});
resolve();
});
}
}
app.module.ts
export function initializeCategoryService(catService: CategoryService) {
return (): Promise<any> => {
return catService.Init();
}
}
#NgModule({
declarations: [
AppComponent,
HomeComponent,
StoriesFilterPipe,
ViewStoryComponent,
ViewCatComponent,
FrontEndComponent,
SearchComponent,
BackEndComponent,
CrudStoryFormComponent,
CrudStoryComponent,
JwPaginationComponent,
CrudCatComponent,
CrudCatFormComponent,
CrudCatSearchResultComponent,
CatListComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
],
providers: [
StoryService,
CategoryService,
{
provide: APP_INITIALIZER, useFactory: initializeCategoryService, deps: [CategoryService], multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
What I will suggest is to use Observable
like in your category service
import { Observable, Subject } from 'rxjs';
export class CategoryService {
private loadDataSub = new Subject<any>();
loadDataObservable$ = this.loadDataSub.asObservable();
emitLoadDataSuccess() {
this.loadDataSub.next();
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
this.emitLoadDataSuccess(); // here we are emitting event
});
resolve();
});
}
}
And In your component
export class AppComponent implements OnInit {
constructor(private categoryService: CategoryService) {
this.categoryService.loadDataObservable$.subscribe(() => {
// here you can get data, this will only trigger when data is loaded from API
});
}
}
This is common case - i.e. you shows page while data is not avaliable yet - at slow and bad connections for instance, and it can do even more - connection was broken and data was nto recieved.
So, your page should be able to show not only data recieved, but also two another states: loading and error.
(So the advise is "add loader").
// data.service.ts
import { Injectable } from "#angular/core";
import { HttpClient, HttpClientModule } from "#angular/common/http";
#Injectable()
export class DataService {
private _categories = [];
constructor(private http: HttpClient) {}
get categories() {
return this._categories;
}
getData(): Promise<any[]> {
return new Promise<any[]>(resolve => {
this.http.get('https://api.myjson.com/bins/18qku4').subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
resolve(this._categories);
});
});
}
}
// app.module.ts
import { NgModule, APP_INITIALIZER } from "#angular/core";
import { BrowserModule } from "#angular/platform-browser";
import { FormsModule } from "#angular/forms";
import { RouterModule } from "#angular/router";
import { ListDataComponent } from "./list-data/list-data.component";
import { AppComponent } from "./app.component";
import { DataService } from "./data.service";
import { HttpClientModule } from "#angular/common/http";
import {DetailComponent} from './detail/detail.component'
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot([
{ path: "", component: ListDataComponent },
{ path: "detail", component: DetailComponent }
])
],
declarations: [AppComponent, ListDataComponent,DetailComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
//list-data.component.ts
import { Component, OnInit } from "#angular/core";
import { DataService } from "../data.service";
#Component({
selector: "app-list-data",
templateUrl: "./list-data.component.html",
styleUrls: ["./list-data.component.css"],
providers: [DataService],
})
export class ListDataComponent implements OnInit {
categories = [];
constructor(service: DataService) {
service.getData().then(data => {
debugger;
this.categories = data;
});
}
ngOnInit() {}
}
There are alternatives to resolve this issue:
One is you can use a loader which you can display until the service call finishes.
Second is you can use *ngIf="categories?.length" which will keep your component hides until your service call finishes.
I hope it will resolve your issue.

Why isn't http request being picked up in angular test

I have a simple service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { ApiConfiguration } from '../../api/api-configuration';
import { BaseService } from '../../api/base-service';
import { Observable } from 'rxjs';
const TIMER = 120000;
#Injectable({
providedIn: 'root'
})
export class AppService extends BaseService {
constructor(
config: ApiConfiguration,
http?: HttpClient,
) {
super(config, http);
}
getContextualHelp(id, helpType): Observable<Object> {
return this.http.get(`${this.rootUrl}/api/v2/contextual-help`, {
params: { id, helpType }
});
}
}
I have tests that make sure the service returns some mocked data:
import { AppService } from '../app.service';
import { TestBed } from '#angular/core/testing';
import { HttpTestingController, HttpClientTestingModule } from '#angular/common/http/testing';
import { StoreModule } from '#ngrx/store';
import { ApiConfiguration } from '../../../api/api-configuration';
fdescribe('app.service', () => {
let service: AppService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
StoreModule.forRoot({}),
],
providers: [
AppService,
{
provide: ApiConfiguration, useValue: ''
},
]
});
service = TestBed.get(AppService);
httpMock = TestBed.get(HttpTestingController);
});
describe('getContextualHelp', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
it('calls the contextual help route with the expected params', () => {
const mockData = '<div></div>';
service.rootUrl = 'https://www.mock.com';
service.getContextualHelp('foo', 'bar').subscribe((html) => {
expect(html).toEqual('<div></div>');
});
const req = httpMock.expectOne('https://www.mock.com/api/v2/contextual-help');
req.flush(mockData);
});
});
});
It seems like I have everything hooked up correctly but when I run the test I get:
Error: Expected one matching request for criteria "Match URL: https://www.mock.com/api/v2/contextual-help", found none.
When I debug the test and go into the code context I see that http client is being called, and it's being called with the url I expect. So I guess somehow I misconfigured the test modules somehow? Does anyone see what I might have done wrong?
Note that expectOne() matches the url as well as the url params. Try to pass the complete url to the function, like:
const req = httpMock.expectOne('https://www.mock.com/api/v2/contextual-help?id=foo&helpType=bar');
Take a look at this question and the answer.

Angular2 Loading the api configuration from the server side before the app bootstrap starts shows

I am using the angular 2 web app. In that, i am getting app configuration details from the api before the app bootsrap. For that i searched on the browser and find out these links
How to call an rest api while bootstrapping angular 2 app
https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9
And followed them. In the angular-cli and also on the browser console i didn't get any error. But my page comes empty with loading text, that was i displayed in the before loading angular.
Here is my code
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { HttpModule, Http} from '#angular/http';
import { SocketIoModule, SocketIoConfig } from 'ng2-socket-io';
import { SharedModule } from './shared/shared.module';
import { TranslateModule, TranslateLoader} from '#ngx-translate/core';
import { TranslateHttpLoader} from '#ngx-translate/http-loader';
import { AppRoutingModule } from './app.route';
import { AppComponent } from './app.component';
import { ChatComponent } from './chat/chat.component';
import { ChatService } from './chat/chat.service';
import { AddressComponent } from './address/address.component';
import { HomeComponent } from './home/home.component';
import { SettingService } from './shared/service/api/SettingService';
import { FilterByPipe } from './filterPipe';
import { Ng2ScrollableModule } from 'ng2-scrollable';
import { AppConfig } from './app.config';
import {Injectable} from '#angular/core';
import {Observable} from 'rxjs/Rx';
const config: SocketIoConfig = { url: 'http://192.168.1.113:7002', options: {} };
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, "http://192.168.1.114:7001/frontend-translation/", "");
}
export function SettingServiceFactory(setting: SettingService) {
return () => setting.load();
//return true;
}
#NgModule({
declarations: [
AppComponent,
ChatComponent,
AddressComponent,
HomeComponent,
FilterByPipe
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule,
Ng2ScrollableModule,
SocketIoModule.forRoot(config),
SharedModule,
TranslateModule.forRoot({
loader: {
provide : TranslateLoader,
useFactory: HttpLoaderFactory,
deps : [Http]
}
}),
],
providers: [
ChatService,
AppConfig,
SettingService,
{
provide : APP_INITIALIZER,
useFactory: SettingServiceFactory,
deps : [SettingService],
multi : true
}
/*SettingService,
{
provide : APP_INITIALIZER,
useFactory: (setting:SettingService) => () => setting.load(),
//deps : SettingService,
multi : true
}*/
],
bootstrap: [AppComponent]
})
export class AppModule { }
SettingService.ts
In this file, i am loading the api configuration from the server side
import {Injectable} from '#angular/core';
import {Http, Response} from '#angular/http';
import {Observable} from 'rxjs/Rx';
#Injectable()
export class SettingService {
public setting;
constructor(private http: Http) {}
/**
* Retrieves the setting details of the website
* #return {Object} language tranlsation
*/
public load()
{
return new Promise((resolve, reject) => {
this.http
.get('http://192.168.1.114:7001/settings')
.map( res => res.json() )
.catch((error: any):any => {
console.log('Configuration file "env.json" could not be read');
resolve(true);
return Observable.throw(error.json().error || 'Server error');
})
.subscribe( (envResponse) => {
this.setting = envResponse;
});
});
}
}
When i replace the load() api service within below code it works fine
public load() {
return {
"test": "works well"
}
}
But with api call, it doesn't.
What i found was, when i am returning the json object it works, but when i am making api call return the promise object it won't. I don't know how to solve this.
Thanks in advance.
My project was little bigger, so i can't put in plunker
Try to use promise object like this:
load(): Promise<Object> {
var promise = this.http.get('./app.json').map(res => res.json()).toPromise();
promise.then(res=> this.label= res);
return promise;
}

ngModel not updated with input value angular 2 test karma

I have a problem during my units test with angular 2. I just want to test an input to check if my model can't be more than 16 chars. (I have indeed use a maxlength in my html). So I enter a new value in my input and use dispatchEvent to set the input in the model but the model is nether update...
Here is my test :
/* tslint:disable:no-unused-variable */
import { EditRequestComponent } from './edit-request.component';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { __platform_browser_private__ as _ } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { MvpSelector } from './mvpSelector/mvpSelector.component';
import { MockBackend, MockConnection } from '#angular/http/testing';
import { FormsModule } from '#angular/forms';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpModule, Http, BaseRequestOptions, Response } from '#angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryWebApiService } from '../../services/in-memory.service';
import { MyDatePickerModule } from 'mydatepicker/dist/my-date-picker.module';
import { ReferentialService, RequestService, ConfigurationService } from '../../services';
import { MvpRequestClass } from '../../models';
//////// SPECS /////////////
describe('EditRequestComponent', function () {
let comp: EditRequestComponent;
let fixture: ComponentFixture<EditRequestComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ReferentialService,
ConfigurationService,
RequestService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
},
deps: [ MockBackend, BaseRequestOptions ]
},
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: {id: 1},
data: {request: new MvpRequestClass()}
}
}
}
],
declarations: [ MvpSelector, EditRequestComponent ],
imports: [ FormsModule, FormsModule, MyDatePickerModule,
HttpModule ]
}).compileComponents();
fixture = TestBed.createComponent(EditRequestComponent);
comp = fixture.componentInstance;
});
it('should create component', () => expect(comp).toBeDefined() );
it('should have a mvpRequestComponent', () =>
{
fixture.detectChanges();
expect(comp.mvpReq).toBeDefined();
});
it('should not have more than 16 char in moInCharge input', fakeAsync(() => {
comp.mvpReq.moInCharge = "oldValue";
fixture.detectChanges();
var field = fixture.debugElement.query(By.css('.input-moInCharge')).nativeElement;
field.value = "aaaaaaaaaaaaaaaaaaaaaa";
_.getDOM().dispatchEvent(field, _.getDOM().createEvent("input"));
tick();
console.log(field.value);
expect(comp.mvpReq.moInCharge.length).toBeLessThanOrEqual(16);
}));
});
Here are the Log print in my console:
LOG LOG: 'oldValue'
You can also see that I have a weird way to use dispatchEvent. That's because when I import and use it directly from '#angular/platform-browser/testing/browser_util' I have an error message :
Uncaught SyntaxError: Unexpected token import
at webpack:///~/#angular/platform-browser/testing/browser_util.js:8:0 <- karma.entry.js:49907
It seems that there was imports directly in the file browser_util.d.ts that our webpack doesn't appreciate. But the dispatchEvent function is sensibly used the same way. So I don't understand why the model doesn't update.
Here are few test that I made to make it work:
use async() function rather than fakeAsync
use the dispatchEvent function of MDN
use fixture.whenStable() with a then()
change the number of 'a' to check if the value doesn't update because
there are too many
a combination of all this points
And that's all. I must admit that I don't know how to proceed differently to make it work. If you can help me or you want more details, don't hesitate.
Thanks in advance.

Categories