The main idea for testing contianer component is to make sure it setup everythings correctlly. Call the onInit() lifecycle first, then the variables have the right value. Methods will be called with the right params.
Container component:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormArray, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/forkJoin'; import { Product, Item } from '../../models/product.interface'; import { StockInventoryService } from '../../services/stock-inventory.service'; @Component({ selector: 'stock-inventory', styleUrls: ['stock-inventory.component.scss'], template: ` <div class="stock-inventory"> <form [formGroup]="form" (ngSubmit)="onSubmit()"> <stock-branch [parent]="form"> </stock-branch> <stock-selector [parent]="form" [products]="products" (added)="addStock($event)"> </stock-selector> <stock-products [parent]="form" [map]="productsMap" (remove)="removeStock($event, i)"> </stock-products> <div class="stock-inventory__buttons"> <button type="submit" [disabled]="form.invalid"> Order stock </button> </div> <pre>{{ form.value | json }}</pre> </form> </div> ` }) export class StockInventoryComponent implements OnInit { products: Product[]; productsMap: Map<number, Product>; form = this.fb.group({ store: this.fb.group({ branch: '', code: '' }), selector: this.createStock({}), stock: this.fb.array([]) }); constructor( private fb: FormBuilder, private stockService: StockInventoryService ) {} ngOnInit() { const cart = this.stockService.getCartItems(); const products = this.stockService.getProducts(); Observable .forkJoin(cart, products) .subscribe(([cart, products]: [Item[], Product[]]) => { const mapInfo = products.map<[number, Product]>(product => [product.id, product]); this.products = products; this.productsMap = new Map<number, Product>(mapInfo); cart.forEach(item => this.addStock(item)); }); } createStock(stock) { return this.fb.group({ product_id: (parseInt(stock.product_id, 10) || ''), quantity: (stock.quantity || 10) }); } addStock(stock) { const control = this.form.get('stock') as FormArray; control.push(this.createStock(stock)); } removeStock({ group, index }: { group: FormGroup, index: number }) { const control = this.form.get('stock') as FormArray; control.removeAt(index); } onSubmit() { console.log('Submit:', this.form.value); } }
Service:
import { Injectable } from '@angular/core'; import { Http, Response, URLSearchParams } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; import { Product, Item } from '../models/product.interface'; @Injectable() export class StockInventoryService { constructor(private http: Http) {} getCartItems(): Observable<Item[]> { return this.http .get('/api/cart') .map((response: Response) => response.json()) .catch((error: any) => Observable.throw(error.json())); } getProducts(): Observable<Product[]> { return this.http .get('/api/products') .map((response: Response) => response.json()) .catch((error: any) => Observable.throw(error.json())); } }
Test:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { DebugElement } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { StockInventoryComponent } from './stock-inventory.component'; import { StockBranchComponent } from '../../components/stock-branch/stock-branch.component'; import { StockCounterComponent } from '../../components/stock-counter/stock-counter.component'; import { StockProductsComponent } from '../../components/stock-products/stock-products.component'; import { StockSelectorComponent } from '../../components/stock-selector/stock-selector.component'; import { StockInventoryService } from '../../services/stock-inventory.service'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; const products = [{ id: 1, price: 10, name: 'Test' }, { id: 2, price: 100, name: 'Another test'}]; const items = [{ product_id: 1, quantity: 10 }, { product_id: 2, quantity: 5 }]; TestBed.initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); class MockStockInventoryService { getProducts() { return Observable.of(products); } getCartItems() { return Observable.of(items); } } describe('StockInventoryComponent', () => { let component: StockInventoryComponent; let fixture: ComponentFixture<StockInventoryComponent>; let el: DebugElement; let service: StockInventoryService; beforeEach(() => { TestBed.configureTestingModule({ imports: [ ReactiveFormsModule ], declarations: [ StockBranchComponent, StockCounterComponent, StockProductsComponent, StockSelectorComponent, StockInventoryComponent ], providers: [ {provide: StockInventoryService, useClass: MockStockInventoryService } ] }) fixture = TestBed.createComponent(StockInventoryComponent) component = fixture.componentInstance; el = fixture.debugElement; service = el.injector.get(StockInventoryService) }) it('should call through tow service funs when init', () => { spyOn(service, 'getCartItems').and.callThrough(); spyOn(service, 'getProducts').and.callThrough(); component.ngOnInit(); expect(service.getCartItems).toHaveBeenCalled(); expect(service.getProducts).toHaveBeenCalled(); }) it('should store the response into products', () => { component.ngOnInit(); expect(component.products).toEqual(products) }) it('should set producetsMap', () => { component.ngOnInit(); expect(component.productsMap.get(1)).toEqual(products[0]); expect(component.productsMap.get(2)).toEqual(products[1]); }) it('should call addStock with the right param', () => { spyOn(component, 'addStock'); component.ngOnInit(); expect(component.addStock).toHaveBeenCalledWith(items[0]); expect(component.addStock).toHaveBeenCalledWith(items[1]); }) });