I have a component with tabs. I need to navigate to a tab and then test the viewable inputs, etc. I can see from the console log that the actions that I am taking are calling the function that I am spying on using the process below. I can also see that this is happening in the correct order.
Unfortunately, this produces a successful test with no errors, there is no expectation found within the test according to ng test. (SPEC HAS NO EXPECTATIONS)
Placing the expectation outside of the whenStable command makes the expectation run before the blur command.
it('should call to save changes when project name is blurred',
fakeAsync(() => {
component.ngOnInit();
tick();
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-tab-label'));
console.log(tabLabels);
tabLabels[1].triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
const projectName = fixture.debugElement.query(By.css('#projectName'));
console.log(projectName);
let mySpy = spyOn(component, 'saveProject');
projectName.triggerEventHandler('blur', null);
console.log('Lowered expectations');
expect(mySpy).toHaveBeenCalled();
});
})
);
Here is the HTML associated with the test.
<!-- HEADER -->
<div class="header accent" fxLayout="row"></div>
<!-- / HEADER -->
<!-- CONTENT -->
<div class="content">
<!-- CENTER -->
<div class="center p-24" fusePerfectScrollbar>
<!-- CONTENT -->
<div class="content p-24" style="box-shadow:0px 0px 0px rgba(0,0,0,0) !important;">
<mat-tab-group fxFlex="80%"
style="margin:0px 10%">
<mat-tab id="projectSelectionTab" label="Project Selection">
<div fxFlex="80%"
fxLayout="column"
style="margin:0px 10%"
*ngIf="versionList && projectList">
<h1 class="mt-32 mb-20">Select a Project</h1>
<mat-divider></mat-divider>
<div *ngIf="projectList.length==0">
You currently have no designs. Go to the marketplace and select a design to add to your list of projects.
</div>
<table class="displayTable">
<tr (click)="setCurrentProject( project ) "
target="_blank"
class="projectRow"
[ngClass]="{'currentItem' : project==currentProject}"
*ngFor="let project of projectList">
<td class="mat-subheading-2"
style="width:10%">
<mat-icon style="cursor:pointer"
matTooltip="Open in design studio"
[routerLink]="['/designStudio/project/'+project.uid]">
color_lens
</mat-icon>
<mat-icon style="cursor:pointer"
matTooltip="View Quotes for this project"
[routerLink]="['/invoice/'+versionList[versionList.length-1]['uid']]">
attach_money
</mat-icon>
</td>
<td class="mat-subheading-2"
style="width:25%">
{{ project.name }}
</td>
<td class="mat-subheading-2"
style="width:20%">
{{ project.dateCreated | date:'short' }}
</td>
<td class="mat-body-1" >
<span style="font-weight:bold; margin-right:10px;">Type : {{project.designType}}</span>
<span style="font-weight:bold; margin-right:10px;">Versions : {{project.versions.length}}</span>
{{ project.description }}
</td>
</tr>
</table>
</div>
</mat-tab>
<mat-tab id="projectDataTab" label="Project Data" flex="100%">
<div flex="100%"
layout="column"
style="width:100%"
*ngIf="currentProject.uid">
<h2 class="mt-32 mb-20">
Project Data -
<mat-icon style="cursor:pointer"
matTooltip="Open in design studio"
[routerLink]="['/designStudio/project/'+currentProject.uid]">
color_lens
</mat-icon>
</h2>
<!-- TOP ROW OF PROJECT DATA -->
<div fxLayout="row" fxLayoutAlign="center" flex="100%">
<div fxFlex="50%"fxLayout="column" class="text-center">
<mat-form-field appearance="outline" floatLabel="always" class="w-100-p">
<mat-label>Project Name</mat-label>
<input matInput
id="projectName"
class="form-control"
placeholder="Product Name"
name="projectName"
#projectName="ngModel"
[(ngModel)]="currentProject['name']"
(blur)="saveProject()"
minlength="5"
maxlength="150"
required>
</mat-form-field>
<div [hidden]="projectName.valid || projectName.pristine || !projectName.errors?.minlength"
class="alert alert-danger">
Project name is required with at least 5 characters
</div>
<mat-form-field style="min-height:190px" appearance="outline" floatLabel="always" class="w-100-p">
<mat-label>Project Description</mat-label>
<textarea style="min-height:190px"
matInput
id="projectDescription"
placeholder="Product Description"
name="description"
#projectDescription="ngModel"
[(ngModel)]="currentProject['description']"
(blur)="saveProject()"
rows="5"
required
minlength="25">
</textarea>
</mat-form-field>
<div [hidden]="projectDescription.valid || projectDescription.pristine || !projectDescription.errors?.minlength"
class="alert alert-danger">
Project description is required with at least 25 characters
</div>
</div>
<div fxFlex="50" fxLayout="column" fxLayoutAlign="center center">
<div fxFlex="80" style="margin:0px 10%">
<img [src]="(designImageUrl | async)">
</div>
<div fxLayout="row"
fxLayoutAlign="center"
class="w-80-p">
<button mat-raised-button
color="primary"
*ngIf="changesExist"
(click)="saveProject(); changesExist=false;"
style="margin:10px 25%; width:50%">
Save Project Changes
</button>
</div>
</div>
</div>
</div>
<!-- / TOP ROW OF PROJECT DATA -->
</mat-tab>
<mat-tab id="versionDataTab" label="Version Data">
<div layout="column"
flex="100%"
style="width:100%"
*ngIf="currentProject !== undefined">
<h2 class="mt-32 mb-20">Version Data</h2>
<!-- Row showing button to create default version -->
<div fxLayout="row"
fxLayoutAlign="center center"
flex="100%">
<div fxFlex="20" style="margin:20px 5%">
<button mat-stroked-button
color="accent"
(click)="createNewVersion('default')">
New version (default)
</button>
</div>
</div>
<!-- Row showing the version list -->
<div fxLayout="row" fxLayoutAlign="center" flex="100%">
<table class="displayTable"
fxFlex="60" style="margin:0px 5%">
<thead>
<tr>
<th>VERSION NUMBER AND NAME</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let version of versionList; index as j"
class="projectRow"
[ngClass]="{'currentItem' : version==currentVersion}"
(click)="onVersionSelected( j )">
<td>{{j+1}} - {{version.name}}</td>
<td>
<mat-icon style="cursor:pointer"
matTooltip="View Quotes for this version"
[routerLink]="['/invoice/'+version['uid']]">
attach_money
</mat-icon>
<mat-icon style="cursor:pointer"
matTooltip="Make copy as latest version"
(click)="createNewVersion( j )">
add_circle
</mat-icon>
</td>
</tr>
</tbody>
</table>
<!--
<div fxFlex="40" style="margin:0px 5%">
<mat-form-field class="w-100-p ml-10-p mt-20 form-group">
<mat-label>Select a version to see the details</mat-label>
<mat-select (selectionChange)="onVersionSelected(versionIndex)"
name="versionIndex"
#name="ngModel"
[(ngModel)]="versionIndex">
<mat-option *ngFor="let version of versionList; index as i" [value]="i">
{{i+1}} - {{version.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxLayoutAlign="center center" fxFlex="40" style="margin:0px 5%">
<button mat-button class="red-900 mt-8" *ngIf="currentVersion.latest">Submit for purchase</button>
<button mat-button class="primary mt-8" *ngIf="!currentVersion.latest">Recreate as latest version</button>
</div>
-->
</div>
<div fxLayout="row" fxLayoutAlign="center" flex="100%">
<!-- VERSIONS DATA -->
<div fxLayout="column"
class="mt-32"
flex="45%"
style="width:40%; margin:0px 5%">
<h3>Data</h3>
<div fxLayout="row"
class="mt-20 mb-32">
<div fxFlex="50">
Estimated price - {{currentVersion.price}}
</div>
<div fxFlex="50">
Date Created - {{currentVersion.dateCreated | date:'short'}}
</div>
</div>
<mat-form-field appearance="outline"
floatLabel="always"
class="w-100-p">
<mat-label>Version Name</mat-label>
<input matInput
class="form-control"
placeholder="Version Name"
name="vName"
#versionName="ngModel"
(blur)="versionChangesExist=true"
[(ngModel)]="currentVersion.name"
required
minlength="5"
maxlength="150">
</mat-form-field>
<div [hidden]="versionName.valid || versionName.pristine || !versionName.errors?.minlength"
class="alert alert-danger">
Version name is required with at least 5 characters
</div>
<mat-form-field appearance="outline"
floatLabel="always"
class="w-100-p">
<mat-label>Version Description</mat-label>
<textarea matInput
placeholder="Version Description"
class="form-control"
name="versionDescription"
#versionDescription="ngModel"
(blur)="versionChangesExist=true"
[(ngModel)]="currentVersion.description"
rows="5"
required>
</textarea>
</mat-form-field>
<div [hidden]="versionDescription.valid || versionDescription.pristine || !versionDescription.errors?.minlength"
class="alert alert-danger">
Version description is required with at least 25 characters
</div>
</div>
<!--/ VERSION DATA -->
<!-- MEASUREMENT LIST -->
<div fxLayout="column"
class="mt-32"
flex="45%"
style="width:40%; margin:0px 5%">
<div fxLayout="row" fxLayoutAlign="center" flex="100%">
<button mat-raised-button
color="primary"
*ngIf="versionChangesExist"
(click)="saveVersion(); versionChangesExist=false;"
style="margin:10px 15%; width:70%">
Save Version
</button>
</div>
<h3>Measurements</h3>
<table mat-table [dataSource]="currentVersion.measurements"
class="mat-elevation-z8"
style="box-shadow:none;">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let meas"> {{meas.name}} </td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef> Value </th>
<td mat-cell *matCellDef="let meas"> {{meas.value}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayMeas"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplayMeas;"></tr>
</table>
</div>
<!--/ MEASUREMENT LIST -->
</div>
</div>
</mat-tab>
<mat-tab id="designAndPurchaseTab" label="Design and Purchase Status">
<div flex="100%"
layout="column"
style="width:100%"
*ngIf="currentProject !== undefined">
<h2 class="mt-32 mb-20">Design and Purchase Status</h2>
<div fxLayout="row"
fxLayoutAlign="center center">
<div *ngFor="let stage of projectStages; index as j;"
fxLayoutAlign="center center"
[class]="projectStatus[j] ? 'arrow_box_green' : 'arrow_box_red' "
[ngStyle]="{'z-index':1000-j}"
(click)="setSelected( j )">
<span>{{stage}}</span>
</div>
</div>
<div fxLayout="row"
fxLayoutAlign="center center">
<div *ngFor="let stage of projectStages; index as j;"
fxLayoutAlign="center center">
<div [class]="projectStatus[j] ? 'green_right' : '' "
*ngIf="selectedStatus[j]"></div>
<div [class]="!projectStatus[j] ? 'red_right' : '' "
*ngIf="selectedStatus[j]"></div>
<div *ngIf="!selectedStatus[j]"
class="filler"></div>
<div class="filler"></div>
</div>
</div>
<div fxLayout="row"
fxLayoutAlign="center center">
<div *ngFor="let text of stageTexts; index as j;"
fxLayoutAlign="center center">
<div fxFlex="50"
*ngIf="selectedStatus[j] && projectStatus[j]">{{text.done}}</div>
<div fxFlex="50"
*ngIf="selectedStatus[j] && !projectStatus[j]">{{text.notdone}}</div>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
<!-- / CONTENT -->
</div>
<!-- / CENTER -->
</div>
<!-- / CONTENT -->
</div>
whenstable will do when inside of a FakeAsync execution zone as an Async zone should keep track of async work allowing fixture. whenstable to hook into that tracking, at least as I understand it. Or indeed if used and not within an async execution zone at all. So if fixture.
The tick() function simulates the asynchronous passage of time for the timers in the fakeAsync zone in Angular test.
To run the test, you will only need to run the command ng test . This command will also open Chrome and run the test in watch mode, which means your test will get automatically compiled whenever you save your file. In your Angular project, you create a component with the command ng generate component doctor .
We wrap our test spec function in another function called async . 2. We place the tests we need to run after the isAuthenticated promise resolves inside this function. This async function executes the code inside its body in a special async test zone. This intercepts and keeps track of all promises created in its body.
Testing your Angular application helps you check that your app is working as you expect. Before writing tests for your Angular app, you should have a basic understanding of the following concepts:
Angular has another method for us to test asynchronous code via the asyncand whenStablefunctions. Let’s rewrite the above test to use these and then we will explain the differences.
The Angular CLI downloads and installs everything you need to test an Angular application with the Jasmine test framework. The project you create with the CLI is immediately ready to test. Just run the ng test CLI command: The ng test command builds the application in watch mode , and launches the Karma test runner.
Click on a test row to re-run just that test or click on a description to re-run the tests in the selected test group ("test suite"). Meanwhile, the ng test command is watching for changes. To see this in action, make a small change to app.component.ts and save. The tests run again, the browser refreshes, and the new test results appear.
That's because whenStable()
doesn't play well with fakeAsync()
function as it is an async
function's stuff.
For using
fakeAsync()
efficiently, one must rely ontick()
orflush()
.
Maybe changing your test case like this, should work.
it('should call to save changes when project name is blurred',
fakeAsync(() => {
component.ngOnInit();
tick();
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-tab-label'));
console.log(tabLabels);
tabLabels[1].triggerEventHandler('click', null);
fixture.detectChanges();
flushMicrotasks(); // or alternatively flush() or tick(250);
fixture.detectChanges();
const projectName = fixture.debugElement.query(By.css('#projectName'));
console.log(projectName);
let mySpy = spyOn(component, 'saveProject');
projectName.triggerEventHandler('blur', null);
console.log('Lowered expectations');
expect(mySpy).toHaveBeenCalled();
})
);
Replaced
flush()
withflushMicrotasks()
followed byfixture.detectChanges()
, it will allow the DOM to be updated with the response after resolving the Promise. On second thought, you can use this approach withflush()
ortick()
as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With