I have been going through a ton of documentations including their own ng-mocks library here . I am relatively new to this library.
PS: I know other libraries like spectator to do this, or using plain jasmine / jest, but i was trying the same using ng-mocks
to see how its done using this library.
eg: with spectator, it is so easy to write this
it('should enter values on input fields and call the service method', () => {
const service = spectator.inject(StoreService);
const spy = service.addDataToDB.mockReturnValue(of({ id: 45 }));
spectator.typeInElement('cool cap', byTestId('title'));
spectator.typeInElement('33', byTestId('price'));
spectator.typeInElement('try it out', byTestId('desc'));
spectator.typeInElement('http://something.jpg', byTestId('img'));
const select = spectator.query('#selectCategory') as HTMLSelectElement;
spectator.selectOption(select, 'electronics');
spectator.dispatchFakeEvent(byTestId('form'), 'submit');
expect(spy).toHaveBeenCalledWith(mockAddForm);
})
For mat-select
i found a reference from their github repo issues here
Is there a simple way of testing a simple form that has selects, radio buttons and inputs? It is such a common requirement, that I expected a working example without much hassle, but that wasnt the case. I have a very simple template driven form
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<mat-form-field appearance="fill">
<mat-label>Title</mat-label>
<input data-testid="titleControl" name="title" ngModel matInput />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Price</mat-label>
<input data-testid="priceControl" name="price" ngModel matInput />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Description</mat-label>
<input data-testid="descControl" name="description" ngModel matInput />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Image</mat-label>
<input data-testid="imageControl" name="image" ngModel matInput />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Select Category</mat-label>
<mat-select data-testid="categoryControl" name="category" ngModel>
<mat-option value="electronics">Electronics</mat-option>
<mat-option value="jewelery">Jewelery</mat-option>
<mat-option value="men's clothing">Men's clothing</mat-option>
<mat-option value="women's clothing">Women's clothin</mat-option>
</mat-select>
</mat-form-field>
<div class="submit-btn">
<button type="submit" mat-raised-button color="primary">Submit</button>
</div>
</form>
and the class file
export class AddProductComponent implements OnInit {
isAdded = false;
@ViewChild('f') addForm: NgForm;
constructor(private productService: ProductService) { }
onSubmit(form: NgForm) {
const product = form.value;
this.productService.addProductToDB(product).subscribe(
_data => {
this.isAdded = true;
this.addForm.resetForm();
}
)
}
}
and I am trying to test if the user typed anything into the input field, and if so, get it
This is the test case I have so far.
import { EMPTY } from 'rxjs';
import { ProductService } from './../../services/product.service';
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AddProductComponent } from './add-product.component';
import { MockBuilder, MockInstance, MockRender, ngMocks } from 'ng-mocks';
import { MatFormField, MatLabel } from '@angular/material/form-field';
import { MatSelect } from '@angular/material/select';
import { MatOption } from '@angular/material/core';
import { Component, forwardRef } from '@angular/core';
describe('AddProductComponent', () => {
beforeEach(() => {
return MockBuilder(AddProductComponent)
.keep(FormsModule)
.mock(MatFormField)
.mock(MatSelect)
.mock(MatLabel)
.mock(MatOption)
.mock(ProductService, {
addProductToDB: () => EMPTY
})
})
it('should be defined', () => {
const fixture = MockRender(AddProductComponent);
expect(fixture.point.componentInstance).toBeDefined();
})
// THIS IS THE PLACE WHERE I GOT FULLY STUCK..
it('should test the Title control', async () => {
const fixture = MockRender(AddProductComponent);
const component = fixture.componentInstance;
const titleEl = ngMocks.find(['data-testid', 'titleControl']);
ngMocks.change(titleEl, 'cool cap');
fixture.detectChanges();
await fixture.whenStable();
const el = ngMocks.find(fixture, 'button');
ngMocks.click(el);
expect(component.addForm.value).toBe(...)
})
it('should test the image control', () => {.. })
it('should test the price control', () => {.. })
})
i was expecting that, after using the ngMocks.change
to type into the element, calling detectChanges
and upon clicking the submit button, the form submit would have triggered and i will be able to see just the title value in the console.
Something like this
{ title: 'cool cap', price: '', description: '', image: '', category: '' }
UUf! forms are hard to test!!
I dug a bit deeper, and it turned out that the problem is with the late call of
fixture.whenStable()
. It has to be called right afterMockRender
whenFormModule
is used.In this case,
MatInput
can be removedMockBuilder
.