I am a beginner to ionic 2 unit testing. I followed angular 2 documentation (https://angular.io/docs/ts/latest/guide/testing.html) to test my ionic 2 application with karma and jasmine.
But now I am stuck in an error called
'Cannot read property '_getPortal' of undefined'
here is my LocationSearchModal.ts file
import { Component } from '#angular/core';
import { NavController, ViewController } from 'ionic-angular';
import { Location } from '../../services/domain/Location';
import { LocationService } from '../../services/LocationService';
import { LoadingController } from 'ionic-angular';
#Component({
selector: 'location-search-modal',
templateUrl: 'location-search-modal.html'
})
export class LocationSearchModal {
locationList: Array<Location> = new Array<Location>();
selectedLocation: number;
temp: any = "test";
constructor(public navCtrl: NavController, public locationService: LocationService, public viewController: ViewController, public loadingController: LoadingController) {
this.filterLocationsForString();
}
filterLocations(event: any): void {
const searchString: string = event.target.value;
this.filterLocationsForString(searchString);
console.log(this.filterLocationsForString(searchString));
}
filterLocationsForString(searchString?: string) {
let loader = this.loadingController.create({
content: "loading"
});
loader.present();
this.locationService.getLocationsForLikeSearchString(searchString)
.subscribe((result) => {
loader.dismissAll();
this.locationList = result
});
console.log(this.locationList);
}
closeLocationSearch() {
this.locationService.getLocationById(this.selectedLocation)
.subscribe((location) => this.viewController.dismiss(location[0]));
}
}
and I used service called locationService.ts there and this is that service
import { Injectable } from '#angular/core';
import { Location } from './domain/Location';
import { DatabaseAccessor } from '../database/DatabaseAccessor';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class LocationService {
locationList:Array<Location> = new Array<Location>();
constructor(public databaseAccessor: DatabaseAccessor) {}
getLocationsForLikeSearchString(searchString: string) : Observable<Array<Location>> {
const searchValue = (searchString == null) ? '%' : searchString.trim() + '%';
return <Observable<Array<Location>>> Observable.fromPromise(this.databaseAccessor.runSelectQuery(Location, new Location(), 'WHERE name LIKE ?', [searchValue]));
}
getLocationById(id: number): Observable<Location> {
return <Observable<Location>> Observable.fromPromise(this.databaseAccessor.runSelectQuery(Location, new Location(), 'WHERE id = ?', [id]));
}
saveLocations(locations: Array<Location>){
this.databaseAccessor.runInsertBatchQuery(Location.prototype, locations);
}
}
Finally, I wrote a spec.ts file to unit testing and here is that,
import { ComponentFixture, async } from '#angular/core/testing';
import { LocationSearchModal } from './LocationSearchModal';
import { LocationService } from '../../services/LocationService';
import { TestUtils } from '../../test';
import { TestBed } from '#angular/core/testing';
import { App, NavController, Platform, Config, Keyboard, Form, IonicModule, GestureController, ViewController, LoadingController } from 'ionic-angular';
import { ConfigMock } from '../../mocks';
import { TranslateModule } from 'ng2-translate';
import { DatabaseAccessor } from '../../database/DatabaseAccessor';
let comp: LocationSearchModal;
let fixture: ComponentFixture<LocationSearchModal>;
let instance: any = null;
describe('LocationSearchModal', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LocationSearchModal], // declare the test component
providers: [App, Platform, Form, Keyboard, NavController, GestureController, LoadingController, LocationService, DatabaseAccessor,
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
{ provide: Config, useClass: ConfigMock },
],
imports: [
IonicModule,
TranslateModule.forRoot(),
],
});
fixture = TestBed.createComponent(LocationSearchModal);
comp = fixture.componentInstance;
}));
console.log(comp);
it('Testing Location Component', () => {
expect(comp.temp).toBe('test');
})
});
when I am running the following error comes from the terminal.
(my unit testing configuration are correct and I tested it with another simple .spec.ts file)
the error
SUMMARY:
✔ 1 test completed
✖ 1 test failed
FAILED TESTS:
LocationSearchModal
✖ Testing Location Component
Chrome 54.0.2840 (Linux 0.0.0)
Failed: Error in ./LocationSearchModal class LocationSearchModal_Host - inline template:0:0 caused by: Cannot read property '_getPortal' of undefined
TypeError: Cannot read property '_getPortal' of undefined
at App.present (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/ionic-angular/components/app/app.js:78:0 <- src/test.ts:2091:35)
at Loading.present (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/ionic-angular/components/loading/loading.js:31:0 <- src/test.ts:38779:26)
at LocationSearchModal.filterLocationsForString (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/src/pages/location-search/LocationSearchModal.ts:9:4184 <- src/test.ts:18993:4170)
at new LocationSearchModal (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/src/pages/location-search/LocationSearchModal.ts:9:3407 <- src/test.ts:18993:3391)
at new Wrapper_LocationSearchModal (/DynamicTestModule/LocationSearchModal/wrapper.ngfactory.js:7:18)
at _View_LocationSearchModal_Host0.createInternal (/DynamicTestModule/LocationSearchModal/host.ngfactory.js:16:35)
at _View_LocationSearchModal_Host0.AppView.create (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/#angular/core/src/linker/view.js:84:0 <- src/test.ts:52350:21)
at _View_LocationSearchModal_Host0.DebugAppView.create (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/#angular/core/src/linker/view.js:294:0 <- src/test.ts:52560:44)
at ComponentFactory.create (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/#angular/core/src/linker/component_factory.js:152:0 <- src/test.ts:32035:36)
at initComponent (webpack:/media/dilanka/Stuff/CODE%20BASE/Inspection/Unit%20Testing/Inspection-Rewrite/~/#angular/core/bundles/core-testing.umd.js:855:0 <- src/test.ts:7416:53)
Mock the LoadingController if the problem is from using LoadingController
export class LoadingControllerMock {
_getPortal(): any { return {} };
create(options?: any) {
return new LoadingMock()
};
}
class LoadingMock {
present() { };
dismiss() { };
dismissAll() { };
}
Import the Mock and the actual from wherever
import { LoadingController } from 'ionic-angular';
import { LoadingControllerMock } from '../../../../test-config/mocks-ionic';
Substitute
providers: [
{ provide: LoadingController, useClass: LoadingControllerMock }
]
I Solved this problem finally. I used a mock and defined required methods in that mock. Then It works :)
here is an example for a mock.
export class ViewControllerMock {
public _setHeader(): any { return {} };
public _setNavbar(): any { return {} };
public _setIONContent(): any { return {} };
public _setIONContentRef(): any { return {} };
}
then have to import that mock into your .spec.ts file as follows
import {ViewControllerMock} from '../../mocks';
then have to define that mock in your providers in spec.ts file as follows
providers: [{ provide: ViewController, useClass: ViewControllerMock}],
Related
I'm trying to unit test a component that requires a Resolver, Router and ActivatedRoute as dependencies. I've tried to use the RouterTestingModule and mock my resolver to provide them in the testing module, but it seems to have some side effects on the creation of the component instance.
Here is the code of my component:
History.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { Subscription } from 'rxjs';
import { Transaction } from '../models/transaction.model';
#Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss']
})
export class HistoryComponent implements OnInit, OnDestroy {
history: Transaction[] = [];
selectedTransaction: Transaction | undefined;
subscription: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private router: Router,
) { }
ngOnInit(): void {
this.history = this.route.snapshot.data.history;
const routeSubscription = this.route.params.subscribe((params) => {
if (params.id) {
this.setSelectedTransaction(+params.id);
}
});
this.subscription.add(routeSubscription);
}
setSelectedTransaction(transactionId: number): void {
const historyTransaction = this.history.find((transaction) => transaction.id === transactionId);
this.selectedTransaction = historyTransaction;
}
displayTransaction(transaction: Transaction): void {
this.router.navigate(['transactions', transaction.id]);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
And here is the current unit test:
History.spec.ts
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { ActivatedRoute, Resolve, Routes } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { Observable, of } from 'rxjs';
import { Transaction } from '../models/transaction.model';
import { HistoryComponent } from './history.component';
import { HistoryResolver } from './history.resolver';
const mockRawTransactions = [
{
"id":"1",
"created_at":"2016-01-01T08:30:39-0300",
"counterparty_name":"Uber",
"debit":"false",
"credit":"true",
"amount":"44.20",
"currency":"EUR",
"operation_type":"refund",
"attachements":[
{
"url":"https:\/\/fakeimg.pl\/350x200\/?text=Hello"
}
],
}
];
const mockTransactions = mockRawTransactions.map((transaction) => new Transaction(transaction));
class HistoryMockResolver implements Resolve<Transaction[]> {
resolve(): Observable<Transaction[]> {
return of(mockTransactions);
}
}
describe('HistoryComponent', () => {
const historyRoutes: Routes = [
{ path: 'transactions', component: HistoryComponent },
{ path: 'transactions/:id', component: HistoryComponent },
];
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HistoryComponent ],
imports: [
RouterTestingModule.withRoutes(historyRoutes),
],
providers: [
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
},
},
},
{ provide: HistoryResolver, useClass: HistoryMockResolver },
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
console.log('Component', component);
expect(component).toBeTruthy();
});
it('should display transactions', () => {
component.history = mockTransactions;
expect(component.history).toBeDefined();
expect(component.history).toHaveSize(mockRawTransactions.length);
});
it('should display a single transaction', () => {
component.history = mockTransactions;
component.displayTransaction(component.history[0]);
expect(component.selectedTransaction).toBeDefined();
expect(component.selectedTransaction).toEqual(component.history[0]);
});
});
The component is defined and well displayed in the console.log while running the tests in Karma, but Karma raises errors for each test case and evaluates the component as undefined.
Here is the first error:
TypeError: Cannot read property 'history' of undefined
at HistoryComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/history/history.component.ts:22:45)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2486:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2457:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2408:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9207:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9306:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10532:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10557:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:22569:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/testing.js:141:1)
What should I fix in my testing module ?
This is because of this.route.snapshot.data.history and data being undefined as your have not passed it in your mock activated snapshot.
You can update your provider for activated route snapshot in History.spec.ts
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
data: {history: 'something-history-obj'}
},
},
},
Or you can always use this.route.snapshot.data?.history within History.component.ts if it is indeed nullable
I'm doing a Nest.js program but I can't find my dependencies problem. I have searched quite a lot and I have found a lot of answers regarding this problem, but I can't figure out why my code isn´t working. So I have a product module which has his DTO´s, Entity, Controller, Service and module, besides it has an interface for its service.
ProductController
import { Controller, Get } from '#nestjs/common';
import { ProductServiceInterface } from './interface/product.service.interface'
#Controller('products')
export class ProductController {
constructor(private readonly productService: ProductServiceInterface) {}
#Get()
getHello(): string {
return this.productService.test();
}
}
ProductServiceInterface
import { Injectable } from '#nestjs/common';
import {
CreateProductInput,
CreateProductOutput,
} from '../dto/create-product.dto';
import { FindProductOutput } from '../dto/find-product.dto';
export interface ProductServiceInterface {
create(input: CreateProductInput): Promise<CreateProductOutput>;
findProduct(productId: string): Promise<FindProductOutput>;
test();
}
ProductService
import { Injectable } from '#nestjs/common';
import { ProductServiceInterface } from './interface/product.service.interface';
import {
CreateProductInput,
CreateProductOutput,
} from './dto/create-product.dto';
import { Product } from './entity/product.entity';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { FindProductOutput } from './dto/find-product.dto';
//import WooCommerce from '../../config/woocomerce.config';
#Injectable()
export class ProductService implements ProductServiceInterface {
constructor(
#InjectRepository(Product)
private productRepository: Repository<Product>,
) {}
public async create(
productDto: CreateProductInput,
): Promise<CreateProductOutput> {
const product = new Product();
product.name = productDto.name;
product.price = productDto.price;
product.imgUrl = productDto.imgUrl;
const savedProduct = await this.productRepository.save(product);
const productOutput = new CreateProductOutput();
productOutput.id = savedProduct.id;
return productOutput;
}
public async findProduct(idProduct: string): Promise<FindProductOutput> {
const fetchedProduct = await this.productRepository.findOne(idProduct);
const productOutput = new FindProductOutput();
productOutput.id = fetchedProduct.id;
productOutput.imgUrl = fetchedProduct.imgUrl;
productOutput.name = fetchedProduct.name;
productOutput.price = fetchedProduct.price;
return productOutput;
}
public test() {
return 'test'
// WooCommerce.get('products', {
// pero_page: 20,
// }).then((resp) => {
// return resp;
// });
}
}
ProductModule
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
import { Product } from './entity/product.entity';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Module } from '#nestjs/common';
#Module({
imports: [TypeOrmModule.forFeature([Product])],
controllers: [ProductController],
providers: [ProductService],
})
export class ProductModule {}
AppModule
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import configuration from 'src/config/configuration';
import { TypeOrmModule } from '#nestjs/typeorm';
import { TypeOrmConfigService } from 'src/config/typeorm.config.service';
import { ProductModule } from './modules/product/product.module';
#Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
}),
ProductModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I hope this code is enough for knowing where my mistake is, I don't like letting others just resolve this for me but I've been watching my code for hours and can't know how to resolve this dependencies problem.
Interfaces disappear at runtime and becomes {} or Object. Nest uses the parameter type metadata to determine what is supposed to be injected (usually via ClassType.name). You have two options to solve this
Use an abstract class instead of an interface. This makes the class still visible at runtime so ClassType.name still works.
Use #Inject('CustomToken') as the way to set the metadata for what Nest needs to inject. You then need to make sure register the custom provider using something like
{
provide: 'CustomToken',
useClass: ClassToBeUsed
}
Either of these methods should fix your issue.
I want to test a function with condition that comes from a service in ngOnInit. I tried many way but no success. i have all kinds of mistakes.
my component
export class MainSectionComponent implements OnInit {
propertiesFrDb: PropertyPost[];
constructor(
private getPropertiesFrDbService: GetPropertiesFrDbService,
private propertyWarehouseService: PropertyWarehouseService,
private router: Router,
config: NgbCarouselConfig,
private userService: UserService,
private sharedFunctionService: SharedFunctionService,
private returnResponseAfterUserLoginService: ReturnResponseAfterUserLoginService,
private localStorageService: LocalStorageServiceService,
private dialogLoginService: DialogLoginService,
#Inject(PLATFORM_ID) private platformId: Object
) {
// this.isBrowser = isPlatformBrowser(platformIds);
}
ngOnInit() {
this.getPropertiesFrDb();
}
getPropertiesFrDb() {
if (this.propertyWarehouseService.currentValuesProperties) {
this.propertyWarehouseService.getPropertyHome$.subscribe(
prop => {
console.log(prop);
return this.propertiesFrDb = prop
}
)
} else {
this.getPropertiesFrDbService.getHomeProperties()
.subscribe(property => {
// console.log(property['results']);
this.propertyWarehouseService.setPropertiesHome(property['results'])
return this.propertiesFrDb = property['results']
},
)
}
}
I want to test this.getPropertiesFrDb() in ngOnInit
i would like to test the case with this.propertyWarehouseService.currentValuesProperties !== ''and checked that this.getPropertiesFrDbService.getHomeProperties() is called and checked the value of propertiesFrDb
and my spec.ts file
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { MainSectionComponent } from './home-properties.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { MaterialModule } from 'src/app/material/material.module';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { GetPropertiesFrDbService } from 'src/app/services/getPropertiesFromDb/get-properties-fr-db.service';
import { MOCKPROPERTIES, MockPropertyWarehouseService } from 'src/app/mocks/property-post';
import { NgxPaginationModule, PaginatePipe } from 'ngx-pagination';
import { PropertyWarehouseService } from 'src/app/services/propertyWarehouse/property-warehouse.service';
import { BsDropdownModule } from 'ngx-bootstrap';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { StorageServiceModule } from 'angular-webstorage-service';
import { of } from 'rxjs/internal/observable/of';
fdescribe('MainSectionComponent', () => {
let component: MainSectionComponent;
let fixture: ComponentFixture<MainSectionComponent>;
const PROPERTYMODEL = MOCKPROPERTIES;
const spyPropertyWarehouseService = jasmine.createSpyObj('spypropertyWarehouseService', ['currentValuesProperties', 'getPropertyHome$']);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxPaginationModule,
BsDropdownModule.forRoot(),
NgbModule,
StorageServiceModule,
],
declarations: [
MainSectionComponent,
],
providers: [
{
provide: PropertyWarehouseService,
useValue: spyPropertyWarehouseService
}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MainSectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', (() => {
// console.log('properties', component);
expect(component).toBeTruthy();
}));
it('Should get propertiesFrDb from GetPropertiesFrDbService', async(() => {
spyPropertyWarehouseService.currentValuesProperties.and.returnValue(PROPERTYMODEL);
spyPropertyWarehouseService.getPropertyHome$.and.returnValue(of(PROPERTYMODEL));
expect(component.propertiesFrDb).toBe(PROPERTYMODEL);
console.log('spy',spyPropertyWarehouseService);
}));
});
Try creating a stub as below:
export class PropertyWarehouseServiceStub{
currentValuesProperties = '';
getPropertyHome$ = new BaheviorSubject<any>('someObj');
setPropertiesHome(){ }
}
export class GetPropertiesFrDbServiceStub{
getHomeProperties(){
return of({results: 'val'})
}
}
in component file make the service public in constructor so that we can override some of its behaviors:
constructor(...,
public propertyWarehouseService: PropertyWarehouseService,
public getPropertiesFrDbService: GetPropertiesFrDbService,
....)
and in spec file as :
providers: [
{
provide: PropertyWarehouseService,
useClass: PropertyWarehouseServiceStub
},{
provide: GetPropertiesFrDbService,
useClass: GetPropertiesFrDbServiceStub
}
],
......
....
..
it('should call getPropertiesFrDb() in ngOnInit',()=>{
spyOn(component,'getPropertiesFrDb').and.callThrough();
component.ngOnInit();
expect(component.getPropertiesFrDb).toHaveBeenCalled();
})
it('inside getPropertiesFrDb() should call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is empty,()=>{
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('someObj');
})
it('inside getPropertiesFrDb() should not call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is NOT empty,()=>{
component.propertyWarehouseService.currentValuesProperties = 'Not empty';
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).not.toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).not.toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('val');
})
You can refer to this intro article written by me on Karma-jasmine which contains more article references for several test use cases.
This one is very much similar to what you are looking for. I am planning to write some more articles, in case you wanna follow.
Also, I have no clue on why you are using return as below inside getPropertiesFrDb()
return this.propertiesFrDb = prop
which is of no use because no value of this function has been assigned to any variable inside ngOnInit.
Need bit help in the Angular any version(2,3,4,5), I am trying from last 2 weeks. Any help would be appreciated.
Apologies, due to big code I am not able to add it in Plunker or JSfiddle.
My workflow goes like this
1 - load the metadata.json
2 - read the first value from the metadata.json
3 - load the first json from folder in APP_INITIALIZER
4 - Populate all the values from metadata.json in dropdown
5 - whenever dropdown value changed load the relaevant json and get the objects display in the UI
I have 3 components
Navigation.component (Dropdown change is triggering here)
dashboard.component (data will be changed based on dropdown content)
programmer.component (data will be changed based on dropdown content)
Whenever Dropdown change event is triggered I want to load the data from json.
metadata.json
[
{
"name": "Q_1090",
"value": "project_q_1090.json"
},
{
"name": "Q_1234",
"value": "project_q_1234.json"
},
{
"name": "Q_1528",
"value": "project_q_1528.json"
}
]
app.config.ts
import { Injectable } from '#angular/core';
import { Http } from "#angular/http";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class AppConfig {
config: any;
user: any;
objects: any;
fileName: any;
constructor(private http: Http) {
console.log('ConfigService called.')
}
load(projectName) {
return new Promise((resolve) => {
/** This method: Loads "host_configuration.json" to get the current working environment. */
this.http.get('./assets/host_configuration.json').map(res => res.json())
.subscribe(config => {
console.log('Configuration loaded');
this.config = config;
/** This method: Loads all the objects from json */
let getSummaryParameters: any = null;
getSummaryParameters = this.http.get('./assets/json/' + projectName);
if (getSummaryParameters) {
getSummaryParameters
.map(res => res.json())
.subscribe((response) => {
this.objects = response;
return resolve(true);
});
} else {
return resolve(true);
}
});
});
}
loadMetadata() {
return new Promise((resolve) => {
//reading metadata.json
this.http.get('./assets/metadata.json').map(res => res.json())
.subscribe(fileName => {
console.log('metadata loaded');
this.fileName = fileName;
return resolve(true);
});
});
}
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { RouterModule } from '#angular/router';
import { HttpClientModule } from '#angular/common/http';
import { HttpModule, JsonpModule } from '#angular/http';
import { AppRoutes } from './app.routing';
import { AppConfig } from './app.config';
import { AppComponent } from './app.component';
import { NavigationComponent } from './navigation/navigation.component';
import { SharedModule } from './shared/shared.module';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { BreadcrumbsComponent } from './navigation/breadcrumbs/breadcrumbs.component';
import { TitleComponent } from './navigation/title/title.component';
#NgModule({
declarations: [
AppComponent,
NavigationComponent,
BreadcrumbsComponent,
TitleComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterModule.forRoot(AppRoutes),
SharedModule,
HttpClientModule,
HttpModule,
JsonpModule,
FormsModule
],
providers: [AppConfig,
{
provide: APP_INITIALIZER,
useFactory: (config: AppConfig) => () => config.load('project_q_1234.json'),
deps: [AppConfig],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { Chart } from 'chart.js';
import { AppConfig } from '../../app.config';
declare var Chart;
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [
'./dashboard.component.css'
]
})
export class DashboardComponent implements OnInit {
constructor(public appConfig: AppConfig, private hostConfig: AppConfig, public getSummaryParameters: AppConfig) { }
ngOnInit() {
this.updates();
}
updates() {
//assign all Parameters to objects
this.objects = this.getSummaryParameters.objects;
var JsonData = this.objects.Information.data;
console.log(JsonData["0"]["0"] + " : " + JsonData["0"][1]);
}
}
programmer.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Chart } from 'chart.js';
import { CommonModule } from '#angular/common';
import { NgbModal, ModalDismissReasons } from '#ng-bootstrap/ng-bootstrap';
import { AppConfig } from '../../app.config';
declare function ChangeSortOrder(): any;
#Component({
selector: 'app-simple-page',
templateUrl: './programmer.component.html'
})
export class ProgrammerComponent implements OnInit {
objects;
constructor(public appConfig: AppConfig, private hostConfig: AppConfig, public getSummaryParameters: AppConfig, private modalService: NgbModal) { }
ngOnInit() {
this.updateData();
}
updateData() {
//assign all Parameters to objects
this.objects = this.getSummaryParameters.objects;
}
}
navigation.component.ts
import { Component, ElementRef, OnInit, ViewChild, Injectable, NgModule } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { Http } from "#angular/http";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { AppConfig } from '../app.config';
import { DashboardComponent } from '.././pages/dashboard/dashboard.component';
import { ProgrammerComponent } from '.././pages/programmer/programmer.component';
#Component({
selector: 'app-admin',
templateUrl: './navigation.component.html',
providers: [DashboardComponent, ProgrammerComponent]
})
#Injectable()
export class NavigationComponent implements OnInit {
fileName: any;
selectedfileName: any;
config: any;
objects: any;
constructor(public menuItems: MenuItems, private http: Http, private appConfig: AppConfig, public router: Router,
private hostConfig: AppConfig, public getSummaryParameters: AppConfig, private dashboardComponent: DashboardComponent,
private programmerComponent: ProgrammerComponent) {
}
ngOnInit() {
this.appConfig.loadMetadata().then(fileName => {
this.fileName = this.appConfig.fileName;
//Initial loading for project Drop-down, Fetch first JSON from metadata.json
this.selectedfileName = 'project_q_1234.json';
});
}
refreshApp(projectName) {
this.appConfig.load(projectName).then(objects => {
this.objects = objects;
this.updateData();
//this commented code partially works but data is not loading properlly
//this.dashboardComponent.updates();
//this.programmerComponent.updateData();
//this.qCProgrammerComponent.updateQCData();
});
}
updateData() {
console.log("Dropdown change start");
//load all the host related settings
this.config = this.hostConfig.config;
localStorage.setItem('url', this.config.host);
localStorage.setItem('folder', this.config.folder);
}
As you can't share a demo, I made my own to show how to load data from API / local json you can try from here.
Feel free to ask if it's not the scenario you want / I understand wrong.
DEMO
Here two things are done, first of all, get the metadata from constructor which will load your data on initializing your app, second select a click method in options to get the selected data and then that data can send to the url to get another data.
I don't know which css framework you using I used here angular material 2.
app.component.html
<p>
Using jsonplaceholder.typicode.com API
</p>
<mat-form-field style="width: 100%">
<mat-select placeholder="Select Any Users" [(value)]="selectedUser">
<mat-option *ngFor="let meta of metadata" (click)="getInfoAboutIndividualMeta(meta)" [value]="meta.name">
{{ meta.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field style="width: 100%" *ngIf="selectedUser">
<mat-select placeholder="Select Posts from {{selectedUser}}">
<mat-option *ngFor="let post of posts" (click)="selectedPost(post)" [value]="post.title">
{{ post.title }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-card *ngIf="selectPost">
<h1>{{selectPost?.title}}</h1>
<p [innerHTML]="selectPost?.body"></p>
</mat-card>
app.component.ts
name = 'Angular 6';
metadata: any[];
posts: any[];
selectedUser: string;
selectPost: Object;
constructor(private appConfig: AppConfig) {
this.metadata = [];
this.posts = [];
this.initialize();
}
initialize() {
this.appConfig.getMetadataJSON().subscribe(res => {
this.metadata = res;
this.selectedUser = this.metadata[0].name;
});
}
getInfoAboutIndividualMeta(meta: Object) {
console.log(meta);
const userId = meta.id;
this.appConfig.getIndividualMetadataJSON(userId).subscribe( res => {
this.posts = res;
});
}
selectedPost(post: Object) {
this.selectPost = post;
}
app-config.class.ts
import { Injectable } from '#angular/core';
import { HttpClient } from "#angular/common/http";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs';
#Injectable()
export class AppConfig {
constructor(private httpClient: HttpClient) {
}
public getMetadataJSON(): Observable<any> {
// Due to stackblitz can't get the local access I put this value to another api source
// const apiUrl = './assets/metadata.json'; // You can use this as well
const apiUrl = 'https://jsonplaceholder.typicode.com/users';
return this.httpClient.get(apiUrl);
}
public getIndividualMetadataJSON(userId: number): Observable<any> {
const apiUrl = 'https://jsonplaceholder.typicode.com/posts?userId=' + userId;
return this.httpClient.get(apiUrl);
}
}
I have a very common/simple scenario. I have an http service which makes a call to local json files and get data which will ultimately be replaced by actual api calls.
I want to mock the service and write test on ngOnInit of Component but it seems its not working.
factory-form.component.ts
import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Factory } from './factory';
import { FactoryService } from './factory.service';
#Component({
moduleId: module.id,
selector: 'factory-form',
templateUrl: './factory-form.component.html',
styleUrls: ['./factory-form.component.css'],
providers: [FactoryService]
})
export class FactoryFormComponent implements OnInit {
private model: Factory = new Factory();
countries;
factoryStatuses;
productTypes;
risks;
private errorMessage: string;
private submitted: boolean = false;
private active: boolean = true;
constructor(private factoryService: FactoryService) {
}
ngOnInit(): void {
this.getCountries();
}
private getCountries() {
this.factoryService.getCountry()
.subscribe(countries => this.countries = countries,
error => this.errorMessage = error);
}
onSubmit(): void {
this.submitted = true;
this.factoryService.saveFactory(this.model);
}
}
factory.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Factory } from './factory';
import { IDictionary } from '../shared/dictionary';
import { AppSettings } from '../shared/app.constants';
#Injectable()
export class FactoryService {
constructor(private http: Http) {
}
getCountry(): Observable<IDictionary[]> {
return this.http.get(AppSettings.countryUrl)
.map(this.extractData)
.do(data => console.log("get Countries: " + JSON.stringify(data)))
.catch(this.handleError);
}
private extractData(response: Response) {
let body = response.json();
return body || {};
}
private handleError(error: Response) {
console.log(error);
return Observable.throw(error.json().error || "500 internal server error");
}
}
factory.service.mock.ts
import {provide, Provider} from '#angular/core';
import {FactoryService} from './factory.service';
import * as Rx from 'rxjs/Rx';
export class MockFactoryService extends FactoryService{
public fakeResponse: any = [{"id": 1, "name": "uk"}];
public getCountry(): Rx.Observable<any> {
let subject = new Rx.ReplaySubject()
subject.next(this.fakeResponse);
return subject;
}
public setResponse(response: any): void {
this.fakeResponse = response;
}
public getProvider(): Provider {
return provide(FactoryService, { useValue: this });
}
}
factory-form.component.spec.ts
/* tslint:disable:no-unused-varfoiable */
import { By } from '#angular/platform-browser';
import { DebugElement, provide } from '#angular/core';
import {
beforeEach, beforeEachProviders,
describe, xdescribe,
expect, it, xit,
async, inject,
addProviders,
fakeAsync,
tick,
ComponentFixture,
TestComponentBuilder
} from '#angular/core/testing';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { FactoryService } from './factory.service';
import { MockFactoryService } from './factory.service.mock';
import { FactoryFormComponent } from './factory-form.component';
beforeEachProviders(() => [FactoryFormComponent, provide(FactoryService, { useClass: MockFactoryService })]);
describe('When loading the FactoryFormComponent', () => {
var builder;
beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb;
builder.overrideProviders(FactoryFormComponent,
[
{ provide: FactoryService, useClass: MockFactoryService }
])
.overrideTemplate('<h1>fake tempalte</h1>');
}));
it('should call ngOnInit', async(() => {
let component = builder.createAsync(FactoryFormComponent);
console.log("component?" + JSON.stringify(component));
component.then((fixture: ComponentFixture<FactoryFormComponent>) => {
console.log("inside block");
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
expect(fixture.componentInstance.ngOnInit).toHaveBeenCalled();
expect(fixture.componentInstance.MockFactoryService.getCountry).toHaveBeenCalled();
}).catch((error) => {
console.log("error occured: " + error);
});;
}));
});
this is the output
12 07 2016 16:54:56.314:INFO [watcher]: Changed file "C:/Projects/ethical_resourcing/src/Ethos.Client/dist/app/factory/factory-form.component.spec.js".
12 07 2016 16:54:56.393:INFO [watcher]: Changed file "C:/Projects/ethical_resourcing/src/Ethos.Client/dist/app/factory/factory-form.component.spec.js.map".
12 07 2016 16:54:56.988:WARN [web-server]: 404: /base/dist/vendor/systemjs/dist/system-polyfills.js.map
LOG: 'component?{"_handler":{"resolved":false}}'
Chrome 51.0.2704 (Windows 7 0.0.0): Executed 1 of 1 SUCCESS (0.108 secs / 0.1 secs)
for some reason its passing because its not going to the inner block. I deliberately broke the creatAsync.then() code to see what is coming as component value.
If someone has a better suggestion to mock the service that would be great.
Update: I did try with templateUrl and overriding it but no luck. maybe missing some fundamental thing.
update 2: I am using angular-cli and just call ng test.