I'm a beginner with Angular.
I created a simple application with Angular Bootstrap who open a simple modal.
Before to continue, I want to implements my tests.
My problems :
I would like test this workflow for MainComponent :
Click on the button openModal (or call openModal)
Verify all content of this modal
Simulate/click on dismiss and close, and verify that my functions
getDismissReason and closeModal are called.
My questions :
Q1
When we execute :
expect(modalService.open).toHaveBeenCalled();
How to be sure that the modal is really open or function really called ?
Q2 :
When i try this code :
const h4DebEl : DebugElement = fixtureMainComponent.debugElement.query(By.css('h4'));
Why it's null ?
I think it's because the H4 tag is in element of ModalComponent.
Then, I tested :
fixtureModalComponent = TestBed.createComponent(ModalComponentComponent);
But I have an error provider for activeModal...
Please find bellow my actual code :
MainComponentComponent.component.ts
import { Component, OnInit } from '#angular/core';
import { NgbModal, ModalDismissReasons } from '#ng-bootstrap/ng-bootstrap';
import { ModalComponentComponent } from '../modal-component/modal-component.component';
#Component({
selector: 'app-main-component',
templateUrl: './main-component.component.html',
styleUrls: ['./main-component.component.scss']
})
export class MainComponentComponent implements OnInit {
constructor(
// Use modalService to open a modal
private modalService: NgbModal
) { }
ngOnInit() {
}
openModal() {
const modalRef = this.modalService.open(ModalComponentComponent);
// Define the value #Input person in ModalComponentComponent
modalRef.componentInstance.person = {
name: "Itachi",
lastName: "Uchiwa"
};
modalRef.result.then((result) => {
this.closeModal();
}, (reason) => {
this.dismissReason(reason);
});
}
/**
* Close modal
*/
closeModal() {
console.log('Closed modal');
}
/**
* Dismiss modal
*/
dismissReason(reason: any) {
if (reason === ModalDismissReasons.ESC) {
this.dismissReasonEsc();
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
this.dismissReasonBackdropClick();
} else {
this.dismissReasonUnknownReason();
}
}
dismissReasonEsc() {
console.log('Dismiss called by pressing ESC');
}
dismissReasonBackdropClick() {
console.log('Dismiss called by pressing BACKDROP_CLICK');
}
dismissReasonUnknownReason() {
console.log("Dismiss called");
}
}
MainComponentComponent.component.spec.ts
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { NgbModal, NgbModalRef, ModalDismissReasons } from '#ng-bootstrap/ng-bootstrap';
import { AppModule } from '../app.module';
import { MainComponentComponent } from './main-component.component';
import { ModalComponentComponent } from '../modal-component/modal-component.component';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
describe('MainComponentComponent', () => {
let component: MainComponentComponent;
// Wrapper MainComponentComponent
let fixtureMainComponent : ComponentFixture<MainComponentComponent>;
let modalService: NgbModal;
let modalRef: NgbModalRef;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports : [ AppModule ]
}).compileComponents().then(() => {
modalService = TestBed.get(NgbModal);
fixtureMainComponent = TestBed.createComponent(MainComponentComponent);
component = fixtureMainComponent.componentInstance;
modalRef = modalService.open(ModalComponentComponent);
spyOn(modalService, "open").and.returnValue(modalRef);
spyOn(component, "openModal").and.callThrough();
spyOn(component, "dismissReason").and.callThrough();
spyOn(component, "dismissReasonEsc").and.callThrough();
spyOn(component, "dismissReasonBackdropClick").and.callThrough();
spyOn(component, "dismissReasonUnknownReason").and.callThrough();
spyOn(component, "closeModal").and.callThrough();
});
}));
afterAll(() => {
modalService.dismissAll();
fixtureMainComponent.destroy();
component = null;
modalRef.close();
modalRef = null;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('Open modal', () => {
component.openModal();
expect(modalService.open).toHaveBeenCalled();
});
it('Open modal with click on the button', () => {
fixtureMainComponent.debugElement.query(By.css('#openModalBtn')).triggerEventHandler("click", null);
fixtureMainComponent.detectChanges();
expect(component.openModal).toHaveBeenCalled();
expect(modalService.open).toHaveBeenCalled();
});
it('Check element in modal', () => {
fixtureMainComponent.debugElement.query(By.css('#openModalBtn')).triggerEventHandler("click", null);
fixtureMainComponent.detectChanges();
// Return null
const h4DebEl : DebugElement = fixtureMainComponent.debugElement.query(By.css('h4'));
// const h4HtmlEl : HTMLElement = h4DebEl.nativeElement;
// expect(h4element.textContent).toEqual("Header :");
});
/**
* Check the dismiss method ESC
* To do same for BACKDROP_CLICK, ...
*/
it('Dimiss with ESC', () => {
component.openModal();
modalRef.dismiss(ModalDismissReasons.ESC);
expect(component.dismissReason).toHaveBeenCalled();
expect(component.dismissReasonEsc).toHaveBeenCalled();
});
});
ModalComponentComponent.component.ts
import { Component, Input } from '#angular/core';
import { NgbActiveModal } from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'app-modal-component',
template: `
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Header : {{person.lastName}} {{person.name}}</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Content modal
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Save click')">Save</button>
</div>
`
})
export class ModalComponentComponent {
#Input() person;
constructor(public activeModal: NgbActiveModal) { }
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
import { MainComponentComponent } from './main-component/main-component.component';
import { ModalComponentComponent } from './modal-component/modal-component.component';
#NgModule({
declarations: [
AppComponent,
MainComponentComponent,
ModalComponentComponent
],
imports: [
BrowserModule,
NgbModule.forRoot()
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [ ModalComponentComponent ],
schemas : [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }
Thank you for your help and your time,
Best regards,
Q1 How to be sure that the modal is really open or function really called ?
really open: not the concern of your test, should be tested in ng-bootstrap
or function really called: is the test says its open, then its open :)
Q2
- debugelement: no idea
Mock activemodal
const activeModal: any = jasmine.createSpyObj('activeModal', ['close', 'dismiss']);
Add to testbed providers
{ provide: NgbActiveModal, useValue: activeModal }
Related
I'm currently making a frontend to my API and now it's time to make another component.
I've tried everything, even created a app-routing.module.ts, but it only displays my app.component.html !!
I don't get why ??
My app-routing.module.ts :
import { AppComponent } from './app.component';
import { clientprodComponent } from './clientproduct/clientprod.component';
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
const appTtitle = document.title;
const routes: Routes = [
{path:"", component: AppComponent},
{path:"clientprod", component: clientprodComponent},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
export const routingComponents = [AppComponent, clientprodComponent]
My clientprod.component.ts:
import { CloudProductService } from './cloudproduct.service';
import { CloudProduct } from './cloudproduct';
import { Component, OnInit } from '#angular/core';
import { HttpErrorResponse } from '#angular/common/http';
import { NgForm } from '#angular/forms';
import * as $ from 'jquery';
#Component({
selector: 'app-client',
templateUrl: './clientprod.component.html',
styleUrls: ['./clientprod.component.css']
})
export class clientprodComponent implements OnInit {
public cloudProducts: CloudProduct[] | null;
public editProduct: CloudProduct | null;
public deleteProduct: CloudProduct | null;
constructor(private cloudProductService: CloudProductService){}
ngOnInit(){
this.getCloudProducts();
}
public getCloudProducts():void{
this.cloudProductService.getCloudProducts().subscribe(
(response: CloudProduct[]) =>{
this.cloudProducts = response;
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
public onOpenModal(cloudProduct: CloudProduct | null, mode: string): void {
const container = document.getElementById('main-container');
const button = document.createElement('button');
button.type = 'button';
button.style.display = 'none';
button.setAttribute('data-toggle', 'modal');
if (mode === 'add') {
button.setAttribute('data-target', '#addProductModal');
}
if (mode === 'edit') {
this.editProduct = cloudProduct;
button.setAttribute('data-target', '#updateProductModal');
}
if (mode === 'delete') {
this.deleteProduct = cloudProduct;
button.setAttribute('data-target', '#deleteProductModal');
}
container!.appendChild(button);
button.click();
}
}
My clientprod.component.spec.ts :
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { clientprodComponent } from './clientprod.component';
describe('clientprodComponent', () => {
let component: clientprodComponent;
let fixture: ComponentFixture<clientprodComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ clientprodComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(clientprodComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I don't get it, I have it imported aswell in my app.module.ts...
Why is it just displaying app.component.html, no matter the url ?
Do you have this line bootstrap: [AppComponent], in your app.module ?
This means AppComponent it's the entry point of the application, everything needs to go inside this one.
if you want to load diferent components base on route, you need to:
add <routet-outlet></router-outlet> to your app.component.html
add routes as you did
const routes: Routes = [
{path:"clientprod", component: clientprodComponent},
];
without routing to Appcomponent because all this routing goes inside it.
make sure to import AppRoutingModule in AppModule
Hope it helps :)
This is my code in angular, the functionality is working all fine but the test cases are getting failed. Please tell me what i am doing wrong in the code?
The error I am getting
HeadlessChrome 83.0.4103 (Windows 10.0.0) AboutComponent should create FAILED
TypeError: Cannot read property 'getAboutInfo' of undefined
at **
at AboutComponent.ngOnInit (http://localhost:9876/karma_webpack/src/app/about/about.component.ts:44:28)
at callHook (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3937:1)
at callHooks (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3901:1)
at executeInitAndCheckHooks (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3842:1)
at refreshView (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11795:1)
at renderComponentOrTemplate (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11903:1)
at tickRootContext (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:13379:1)
at detectChangesInRootView (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:13413:1)
at RootViewRef.detectChanges (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:15093:22)
at ComponentFixture._tick (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/testing.js:323:1)
import { async, ComponentFixture, TestBed} from '#angular/core/testing';
import { AboutComponent } from './about.component';
import { AboutService } from './about.service';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { Observable, of } from 'rxjs';
import { I18nService } from 'src/utils/i18n.service';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { AppModule } from './../app.module';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
let dialogSpy: jasmine.Spy;
let app: any;
const mockDialogRef = {
close: jasmine.createSpy('close')
};
let service: any;
const data = '20/04/2019';
let getAboutInfoSpy: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AboutComponent],
imports: [HttpClientTestingModule , AppModule],
providers: [{ provide: AboutService, useValue: service },
I18nService,
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: mockDialogRef}]
}).compileComponents();
}));
beforeEach(async () => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
await fixture.whenStable();
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('infoList should be empty array', () => {
expect(app['dataList'].length).toBe(0);
});
it('when OnInit invoked through service data will return to infoList ', async(() => {
service = fixture.debugElement.injector.get(AboutService);
spyOn(service, 'getAboutInfo').and.returnValue(of(data));
app.ngOnInit();
expect(app['dataList'].length).toBe(3);
}));
it('onCancel should close the dialog', async( () => {
component.closePopup();
expect(mockDialogRef.close).toHaveBeenCalled();
}));
});
import { Component, OnInit, Inject } from '#angular/core';
import { AboutService } from './about.service';
import { Subscription } from 'rxjs';
import { MatDialogRef} from '#angular/material/dialog';
import { I18nService } from 'src/utils/i18n.service';
#Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
private aboutServiceSubscription: Subscription;
dataList: any;
locales: any = {};
translator: any;
constructor(
private dialogRef: MatDialogRef<AboutComponent>,
public aboutService: AboutService,
private i18Service: I18nService) {}
ngOnInit() {
this.translator = this.i18Service.getTranslator();
this.translator.translateObject.subscribe((item: any) => {
this.locales = item;
});
this.aboutServiceSubscription = this.aboutService.getAboutInfo().subscribe((data: any) => {
if (data) {
data = data.split('/');
this.dataList = data;
}
});
}
/**
* Closes the poup
* #memberof AboutComponent
*/
closePopup() {
this.dialogRef.close();
}
}
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AboutService {
constructor(private http: HttpClient) {
}
getAboutInfo() {
return this.http.get('/assets/aboutInfo.txt', {responseType: 'text'})
}
}
The error message indicates that you did not mock AboutService correctly, you passed an undefined to useValue, so the AboutService obtained through the Injector is undefined. You can't use spyOn to a undefined value.
Here is a working example, with irrelevant code removed:
about.component.ts:
import { Component, OnInit } from '#angular/core';
import { AboutService } from './about.service';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-about',
})
export class AboutComponent implements OnInit {
private aboutServiceSubscription: Subscription;
dataList: any;
locales: any = {};
translator: any;
constructor(public aboutService: AboutService) {}
ngOnInit() {
this.aboutServiceSubscription = this.aboutService
.getAboutInfo()
.subscribe((data: any) => {
if (data) {
data = data.split('/');
this.dataList = data;
}
});
}
}
about.service.ts:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class AboutService {
constructor(private http: HttpClient) {}
getAboutInfo() {
return this.http.get('/assets/aboutInfo.txt', { responseType: 'text' });
}
}
about.component.spec.ts:
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { AboutComponent } from './about.component';
import { AboutService } from './about.service';
fdescribe('62700708', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
let aboutServiceSpy: jasmine.SpyObj<AboutService>;
const data = '20/04/2019';
beforeEach(() => {
aboutServiceSpy = jasmine.createSpyObj('AboutService', ['getAboutInfo']);
aboutServiceSpy.getAboutInfo.and.returnValue(of(data));
TestBed.configureTestingModule({
declarations: [AboutComponent],
imports: [HttpClientTestingModule],
providers: [{ provide: AboutService, useValue: aboutServiceSpy }],
}).compileComponents();
});
beforeEach(async () => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('when OnInit invoked through service data will return to infoList ', () => {
expect(aboutServiceSpy.getAboutInfo).toHaveBeenCalledTimes(1);
expect(component.dataList).toEqual(['20', '04', '2019']);
});
});
unit test result:
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.
Im creating a unit test for my confirmation modal that uses MatDialog. my first test is a basic test that the component should be created. here is my code for the spec file.
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { PocitConfirmationModalComponent } from './confirmation-modal.component';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { CommonModule } from '#angular/common';
import { PortalModule } from '#angular/cdk/portal';
import { MaterialModule } from 'src/app/core/material/material.module';
import { BrowserDynamicTestingModule } from '#angular/platform-browser-dynamic/testing';
class MatDialogRefStub {
close(param: string) {}
}
describe('PocitConfirmationModalComponent', () => {
let component: PocitConfirmationModalComponent;
let fixture: ComponentFixture<PocitConfirmationModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
MaterialModule,
PortalModule
],
declarations: [PocitConfirmationModalComponent],
providers: [
{ provide: MatDialogRef, useClass: MatDialogRefStub },
{ provide: MAT_DIALOG_DATA, useValue: {} },
]
}).overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [ PocitConfirmationModalComponent ],
}
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PocitConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
this is the component file that I want to test.
import { Component, OnInit, Inject, ViewEncapsulation } from '#angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { ComponentPortal } from '#angular/cdk/portal';
#Component({
selector: 'pocit-confirmation-modal',
templateUrl: './confirmation-modal.component.html',
styleUrls: ['./confirmation-modal.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class PocitConfirmationModalComponent implements OnInit {
portal: ComponentPortal<any>;
constructor(
public dialogRef: MatDialogRef<PocitConfirmationModalComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
) {
this.portal = new ComponentPortal(this.data.component);
}
ngOnInit() {
}
action(type: string) {
this.dialogRef.close(type);
}
}
after all of these I got this error after running the test.
Error: No component factory found for undefined. Did you add it to #NgModule.entryComponents?
I already added it to entryComponents but still got this error.
//your public data:any model, eg:
const model: any = {
ActionButton: 'Delete',
SupportingText: 'Are you sure?',
};
then in providers:
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: model
}
]
This is my component:
import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, OnDestroy } from '#angular/core';
import { FormBuilder, FormGroup , Validators } from '#angular/forms';
import { Subscription } from 'rxjs';
import { Values } from '../_models/values';
#Component({
selector: 'some',
templateUrl: './my.component.html',
styleUrls: ['./my.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Mycomponent implements OnInit, OnDestroy {
#Input()
values: Array<string>;
#Output()
selectedValues = new EventEmitter<Values>();
private myForm: FormGroup;
#Input()
errorMsg: string;
private selectSubscription: Subscription;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
'selectAll': [false],
'values': [this.values, Validators.required]
});
this.selectSubscription = this.myForm.get('selectAll').valueChanges.subscribe(value => {
this.changeSelection(value);
});
}
submit(): void {
console.log('called');
console.log(this.myForm.value.values);
const theSelectedValues = {
vals: this.myForm.value.values
};
this.selectedValues.emit(theSelectedValues);
}
private changeSelection(selectAll: boolean): void {
if (selectAll) {
const valuesSelect = this.myForm.controls['values'];
valuesSelect.disable();
} else {
this.myForm.controls['values'].enable();
}
}
ngOnDestroy() {
this.selectSubscription.unsubscribe();
}
}
The template:
<form [formGroup]="myForm" (ngSubmit)="submit()">
<fieldset>
<mat-checkbox formControlName="all">Select all</mat-checkbox>
</fieldset>
<fieldset>
<select id="chooser" formControlName="values" multiple>
<option *ngFor="let val of values?.urls" [value]="val">{{val}}</option>
</select>
</fieldset>
<button mat-button [disabled]="myForm.invalid">Go!</button>
</form>
<div *ngIf="errorMsg">{{errorMsg}}</div>
The test
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { Mycomponent } from './my.component';
import { By } from '#angular/platform-browser';
import { FormBuilder } from '#angular/forms';
import { NO_ERRORS_SCHEMA } from '#angular/core';
describe('Mycomponent', () => {
let component: Mycomponent;
let fixture: ComponentFixture<Mycomponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
schemas: [NO_ERRORS_SCHEMA],
declarations: [Mycomponent],
providers: [
FormBuilder ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Mycomponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should emit selected values', () => {
spyOn(component.selectedValues, 'emit');
component.values = ['abc', 'de'];
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('option')).length).toBe(2); // is 0
const formDE = fixture.debugElement.query(By.css('form'));
formDE.triggerEventHandler('ngSubmit', {});
// urls: null
expect(component.selectedValues.emit).toHaveBeenCalledWith({ vals: ['abc', 'de'] });
});
});
The test fails because
a)
component.values = ['abc', 'de'];
Does not lead to the form having two option elements
and b)
expect(component.selectedValues.emit).toHaveBeenCalledWith({ vals: ['abc', 'de'] });
Is called, but with { vals: null }
The code works, the app itself works fine, just the test is failing.
How do I set up the form properly, the #Input element?
I have looked at some blog posts but have not been able to adapt them to my code.
This is because you are using Onpush strategy. When onPush is used change detection is propagating down from the parent component not the component itself.
My suggestion is to wrap your test component inside a host component. There is a pending issue regarding this on Angular's gitHub page you can look it up for further reading.