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 >
<label for="ProductPrice">Product price</label>
<input id="ProductPrice" name="ProductPrice" [(ngModel)]="productPrice" type="text">
</div>
<div >
<input (click)="clear()" type="button" value="Clear">
<input (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();
}
CodePudding user response:
seems like it should be
it('should calculate tax based on base price when save button is clicked', () => {
component.productPrice = 4000;
fixture.detectChanges();
expect( getFormElementValue( fixture.nativeElement, '#ProductPrice')).toEqual( '4000')
});
I'd suggest using the reactive forms instead of [(ngModel)]
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.