Issue
How can I query and fill form fields in a Jest test script? My working environment is Angular 13.
In saw similar posts and was inspired in using alternative answers. But I keep on getting errors that properties don't exist.
My simplified form is:
<form name="frmProduct">
<div class="form-group row">
<label class="col-2" for="ProductPrice">Product price</label>
<input class="col-8 form-control" id="ProductPrice" name="ProductPrice" [(ngModel)]="productPrice" type="text">
</div>
<div class="form-group row">
<input class="col-2 btn btn-warning pl-1" (click)="clear()" type="button" value="Clear">
<input class="col-2 btn btn-success pl-2" (click)="save()" type="button" value="Save">
</div>
</form>
Try-1: My Jest test code is ... having errors that the property does not exist.
describe('ProductFormComponent', () => {
let component: ProductcomponentComponent;
let fixture: ComponentFixture<ProductcomponentComponent>;
let button: HTMLElement;
beforeAll(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
});
beforeEach( () => {
TestBed.configureTestingModule({
declarations: [ProductcomponentComponent],
imports: [FormsModule, ReactiveFormsModule ]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProductcomponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// Below is the synchronous way. I also tried the async way via fakeAsync( .. )
it('should calculate tax based on base price when save button is clicked', () => {
productPrice = 4000;
fixture.detectChanges();
expect( getFormElementValue( fixture.nativeElement, '#ProductPrice')).toEqual( '4000')
});
function getFormElementValue( componentForm: any, selector:string): string {
return componentForm.querySelector( selector).value;
}
});
EDIT: with the help of '@rfprod' solved it. Thank you very much!
Set via component and read via form field:
it('should calculate tax based on base price when save button is clicked', async () => {
component.productPrice = 4000;
fixture.detectChanges();
fixture.whenStable().then( () => {
expect(getFormElementValue(fixture.nativeElement, '#ProductPrice')).toEqual('4000')
});
});
function getFormElementValue( componentForm: any, selector:string): string {
return componentForm.querySelector( selector).value;
}
Setting and reading form fields:
it('should calculate tax based on base price when save button is clicked', async () => {
component.product.BasePrice = 4000;
fixture.detectChanges();
fixture.whenStable().then( () => {
setFormElementValue(fixture.nativeElement, '#BasePrice', 5000)
});
fixture.whenStable().then( () => {
expect(getFormElementValue(fixture.nativeElement, '#BasePrice')).toEqual('5000')
});
});
function setFormElementValue(componentForm: any, selector: string, value: number) {
let element = componentForm.querySelector( selector);
element.value = value;
element.dispatchEvent(new Event('input'));
fixture.detectChanges();
}
Solution
You have a few things wrong in your test. One, your jest test seems incomplete. Like, you need to set the product price on the component property.
Two, when you're getting the price, you need to target the selector a bit better.
Here are a few examples that worked for me:
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { FormComponent } from './form.component';
import { EventEmitter, Output } from '@angular/core';
describe('FormComponent', () => {
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FormComponent ],
imports: [FormsModule],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set the price in the form, when we set it in the model', () => {
component.productPrice = '4000';
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('#ProductPrice').value).toEqual('');
fixture.whenStable()
.then(() => {
expect(fixture.nativeElement.querySelector('#ProductPrice').value).toEqual('4000');
});
});
it('should do this with "waitForAsync"', waitForAsync(() => {
component.productPrice = '4000';
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('#ProductPrice').value).toEqual('');
fixture.whenStable()
.then(() => {
expect(fixture.nativeElement.querySelector('#ProductPrice').value).toEqual('4000');
});
}));
// maybe you wanna create an output and listen for changes?
// like, save will output productPrice + 20%
// your component has:
// @Output() priceWithTax = new EventEmitter<string>();
// and the save method:
// this.priceWithTax.emit( <product price increased by 20 %> )
it('should do this with "waitForAsync"', waitForAsync((done: any) => {
component.priceWithTax
.subscribe((value: number) => {
expect(value).toEqual(4800);
done();
})
component.productPrice = '4000';
fixture.detectChanges();
}));
});
My component template looks like yours:
<form name="frmProduct">
<div class="form-group row">
<label class="col-2" for="ProductPrice">Product price</label>
<input class="col-8 form-control" id="ProductPrice" name="ProductPrice" [(ngModel)]="productPrice" type="text">
</div>
<div class="form-group row">
<input class="col-2 btn btn-warning pl-1" (click)="clear()" type="button" value="Clear">
<input class="col-2 btn btn-success pl-2" (click)="save()" type="button" value="Save">
</div>
</form>
And my component class is like this:
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'ng-jest-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
@Output() priceWithTax = new EventEmitter<number>();
productPrice = '';
constructor() { }
ngOnInit(): void {
console.log('initialized')
}
clear() {
this.productPrice = '';
}
save() {
console.log('PP:', this.productPrice);
const price = parseInt(this.productPrice, 10);
const value = Math.round(price * 1.2)
this.priceWithTax.emit(value)
}
}
These are the results I get:
Edit: here's an updated, sync version
So, just for the sake of completeness, here's an updated version for you, with ReactiveFormsModule. Reactive Forms are synchronous and while they add a bit of boilerplate in one place, you save it in many others.
So my template would have
<input [formControl]="productPriceControl">
instead of
<input [(ngModel)]="productPrice">
My component would have:
class FormComponent {
productPriceControl = new FormControl('');
...
// here's how to read the value from the control
save() {
const currentPrice = this.productPriceControl.value;
// calculate price with tax
}
// here's how to write the value
clear() {
this.productPriceControl.setValue('');
}
...
}
Now, my test looks like this:
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { FormComponent } from './form.component';
import { EventEmitter, Output } from '@angular/core';
describe('FormComponent', () => {
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FormComponent ],
imports: [ ReactiveFormsModule ],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set the price in the form, when we set it in the model', () => {
component.productPriceControl.setValue('4000');
expect(fixture.nativeElement.querySelector('#ProductPrice').value).toEqual('4000');
});
it('should set the price in the control, when we set it in the template', () => {
const input = fixture.nativeElement.querySelector('#ProductPrice');
input.value = '4000';
input.dispatchEvent(new Event('input'));
expect(component.productPriceControl.value).toEqual('4000')
});
});
As you can see, there is no asynchronous code, no waitForAsync
or anything. Everything is synced right away.
Hope that helps for some future work.
Answered By - Zlatko
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.