Angular 6 - Unit Testing Mat-Select - unit-testing

1: The mat-select has 4 values, 1,2,3,4.
The code below works good for the select. So I'd like to share if it helps the readers.
it('check the length of drop down', async () => {
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
trigger.click();
fixture.detectChanges();
await fixture.whenStable().then(() => {
const inquiryOptions = fixture.debugElement.queryAll(By.css('.mat-option-text'));
expect(inquiryOptions.length).toEqual(4);
});
});
2: I need another test to verify the default value in the same
mat-select is 3 or not. When page loads the default value for the drop down is set to 3.
it('should validate the drop down value if it is set by default', async () => {
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
trigger.click();
fixture.detectChanges();
await fixture.whenStable().then(() => {
const inquiryOptions = fixture.debugElement.queryAll(By.css('.mat-option-text'));
const value = trigger.options[0].value;
expect(value).toContain(3);
});
});
Any help is appreciated.

This one is worked for me in Angular 7
const debugElement = fixture.debugElement;
// open options dialog
const matSelect = debugElement.query(By.css('.mat-select-trigger')).nativeElement;
matSelect.click();
fixture.detectChanges();
// select the first option (use queryAll if you want to chose an option)
const matOption = debugElement.query(By.css('.mat-option')).nativeElement;
matOption.click();
fixture.detectChanges();
fixture.whenStable().then( () => {
const inputElement: HTMLElement = debugElement.query(By.css('.ask-input')).nativeElement;
expect(inputElement.innerHTML.length).toBeGreaterThan(0);
});

After some testing I found an answer (at least for my code) and hope, that this is helpful to you as well:
When I looked at the DOM, when the application is running, I noticed that the default value of the mat-select is inside this DOM structure:
<mat-select>
<div class="mat-select-trigger">
<div class="mat-select-value">
<span class="something">
<span class="something">
The value is here!
But in my case, I had a form builder in my .ts file and it was used in ngOnInit(). It seems that the normal TestBed.createComponent(MyComponent) does not call ngOnInit(). So I had to do that in order to get the value. Otherwise there was just a placeholder span.
So, my final code looks like this:
it('should validate the drop down value if it is set by default', async () => {
const matSelectValueObject: HTMLElement = fixture.debugElement.query(By.css('.mat-select-value')).nativeElement;
component.ngOnInit();
fixture.detectChanges();
const innerSpan =
matSelectValueObject.children[0].children[0]; // for getting the inner span
expect(innerSpan.innerHTML).toEqual(3); // or '3', I did not test that
});
By the way I'm using Angular 7, in case this matters.

Helper method for your page object to set the option by text:
public setMatSelectValue(element: HTMLElement, value: string): Promise<void> {
// click on <mat-select>
element.click();
this.fixture.detectChanges();
// options will be rendered inside OverlayContainer
const overlay = TestBed.get(OverlayContainer).getContainerElement();
// find an option by text value and click it
const matOption = Array.from(overlay.querySelectorAll<HTMLElement>('.mat-option span.mat-option-text'))
.find(opt => opt.textContent.includes(value));
matOption.click();
this.fixture.detectChanges();
return this.fixture.whenStable();
}

let loader = TestbedHarnessEnvironment.loader(fixture);
const matSelect = await loader.getAllHarnesses(MatSelectHarness);
await matSelect[0].clickOptions();
const options = await matSelect[0].getOptions();
expect(await options[0].getText()).toMatch("");
expect(await options[1].getText()).toMatch('option1');
expect(await options[2].getText()).toMatch('option2');

const select = await loader.getAllHarnesses(MatSelectHarness);
//test to check there are how many mat selects
expect(select.length).toBe(1);
//open the mat select
await select[0].open();
//Get the options
const options = await select[0].getOptions();
//test to check option length
expect(options.length).toBe(1);
//test to check mat options
expect(await options[0].getText()).toBe('option 1');
expect(await options[1].getText()).toBe('option 2');

Related

vue-test-utils find did not return , cannot call setValue()

I have a textbox inside a model. what I want is to test the model by various functions. at the moment what the error occurs is [vue-test-utils]: find did not return #txtForget, cannot call setValue() on empty Wrapper
Login.vue has a textbox / input box -> I have used vuetify
<v-row class="ma-0 pa-0 mt-5 MultiFormStyle ">
<!-- EMAIL -->
<v-col md="12" sm="12" cols="12" class="">
<ValidationProvider
rules="required|email"
name="EMAIL ADDRESS"
v-slot="{ errors }"
>
<v-text-field
v-model="editedItem.email"
:label="errors[0] ? errors[0] : 'EMAIL ADDRESS'"
:error-messages="errors"
dense
hide-details=""
id="txtForget"
>
</v-text-field>
</ValidationProvider>
</v-col>
</v-row>
</ValidationObserver>
Login.spec.js has a test as follows
test("RESET PASSWORD test", async () => {
let wrapper = mount(Login, {
stubs: ["router-link", "router-view"],
vuetify,
router,
localVue,
});
wrapper.vm.editedItem.email = "admin#gmail.com";
let element_textbox = wrapper.find("#txtForget");
await element_textbox.setValue("test#gmail.com");
expect(wrapper.vm.editedItem.email).toBe("admin#gmail.com");
});
i found the issue and solved it as follows
checking if the model exist
let ForgetModel = wrapper.find("#forgetModel");
expect(ForgetModel.exists()).toBe(true);
then triging the button to open the model
let ForgetPasswordBtn = wrapper.find("button#forgotPasswordBtn");
ForgetPasswordBtn.trigger("click");
await wrapper.vm.$nextTick();
after that find the input element and write a text on it
let element_email = wrapper.find("#txtForget");
await element_email.setValue("test#gmail.com");
finally checking the written value is bonded or not
expect(wrapper.vm.editedItem).toBe("test#gmail.com");
this is the proper method I found from various articles that works on vuetify.
complete code is below
test("RESET PASSWORD test", async () => {
let wrapper = mount(Login, {
stubs: ["router-link", "router-view"],
vuetify,
router,
localVue,
});
let ForgetModel = wrapper.find("#forgetModel");
let ForgetPasswordBtn = wrapper.find("button#forgotPasswordBtn");
ForgetPasswordBtn.trigger("click");
await wrapper.vm.$nextTick();
let element_email = wrapper.find("#txtForget");
await element_email.setValue("test#gmail.com");
expect(ForgetModel.exists()).toBe(true);
expect(wrapper.vm.editedItem).toBe(true);
});

How to unit test the checkbox in Angular2

I have a sample code for checkbox written with Angular2.
<div class="col-sm-7 align-left" *ngIf="Some-Condtion">
    <input type="checkbox" id="mob_Q1" value="Q1" />
    <label for="mob_Q1">Express</label>
</div>
I want to unit test the above checkbox. Like I want to recognize the checkbox and test whether it is check-able. How do I unit test this with Karma Jasmine?
Component, e.g. CheckboxComponent, contains input element. Unit test should looks like:
import {ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {CheckboxComponent} from './checkbox.component';
describe('Checkbox test.', () => {
let comp: CheckboxComponent;
let fixture: ComponentFixture<CheckboxComponent>;
let input: Element;
beforeEach(() => {
TestBed.configureTestingModule(
{
declarations: [CheckboxComponent],
},
);
fixture = TestBed.createComponent(CheckboxComponent);
comp = fixture.componentInstance;
input = fixture.debugElement.query(By.css('#mob_Q1')).nativeElement;
});
it('should click change value', () => {
expect(input.checked).toBeFalsy(); // default state
input.click();
fixture.detectChanges();
expect(input.checked).toBeTruthy(); // state after click
});
});
IS there a need to write fixture.detectChanges()?
I went through the same test without this and it ends with success.
Button 1 is 'checked' by default
const button1 = debugElement.nativeElement.querySelector(selectorBtn1);
const button2 = debugElement.nativeElement.querySelector(selectorBtn2);
...
expect(button1.checked).toBeTruthy();
expect(button2.checked).toBeFalsy();
button2.click();
expect(button1.checked).toBeFalsy();
expect(button2.checked).toBeTruthy();
...
ngModel directive is async one and requires to use asynchronous capabilities of Angular unit testing. Adding async and whenStable functions.
it('checkbox is checked if value is true', async(() => {
component.model = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
const inEl = fixture.debugElement.query(By.css('#mob_Q1'));
expect(inEl.nativeElement.checked).toBe(true);
});
}));
Source LinkLink

Unable to simulate keypress event in Angular 2 unit test (Jasmine)

I am using a directive to get the data from input used as a filter text.
here is my hostlistener in the directive:
#HostListener('input', ['$event.target.value'])
public onChangeFilter(event: any): void {
console.log('input event fired, value: ' + event);
this.columnFiltering.filterString = event;
this.filterChanged.emit({filtering: this.columnFiltering});
}
this code is working perfectly, I am unable to unit test the same.
I have subscribed to the filterChanged EventEmitter, in my unit test to check the value.
I tried simulating keypress event to change value and also tried settings value attribute. None of these is working for me.
here is my spec file:
describe('Table View', () => {
let fixture: ComponentFixture<any>;
let context: TableComponent;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
TableComponent,
],
imports: [TableModule],
});
fixture = TestBed.createComponent(TableComponent);
context = fixture.componentInstance;
});
it('should allow filter', () => {
const element = fixture.nativeElement;
context.config = config;
fixture.detectChanges();
let tableChangeCount = 0;
let tableEvent: any;
context.tableChanged.subscribe((event: any) => {
tableChangeCount++;
tableEvent = event;
});
// Check if table exists
let inputElement = element.querySelectorAll('tr')[1].querySelector('input');
let e = new KeyboardEvent("keypress", {
key: "a",
bubbles: true,
cancelable: true,
});
inputElement.dispatchEvent(e);
});
});
I tried setting value:
let attrs = inputElement.attributes;
inputElement.setAttribute('value', 'abc');
for (let i = attrs.length - 1; i >= 0; i--) {
// Attribute value is set correctly
if (attrs[i].name === 'value') {
console.log(attrs[i].name + "->" + attrs[i].value);
}
}
Can anyone please help me, how can I unit test the same?
I've had some trouble simulating a keypress in a unit test also. But came across an answer by Seyed Jalal Hosseini. It might be what you're after.
If you're attempting to simulate a keypress you can trigger an event by calling dispatchEvent(new Event('keypress')); on the element.
Here is the answer I'm referring to which gives more detail : https://stackoverflow.com/a/37956877/4081730
If you want to set the key that was pressed, this can be done also.
const event = new KeyboardEvent("keypress",{
"key": "Enter"
});
el.dispatchEvent(event);
Further information I've just come across: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
If you wish to use a key code (or "which"), you can do this:
// #HostListener('document:keypress')
const escapeEvent: any = document.createEvent('CustomEvent');
escapeEvent.which = 27;
escapeEvent.initEvent('keypress', true, true);
document.dispatchEvent(escapeEvent);
it('should trigger a TAB keypress event on an element', () => {
const tabKeypress = new KeyboardEvent('keypress', {
// #ts-ignore
keyCode: 9, // Tab Key
cancelable: true
});
const myTableEle = debugEle.nativeElement.querySelector('.your-element');
myTableEle.dispatchEvent(tabKeypress);
fixture.detectChanges();
});
// #ts-ignore :- is to remove TS warning because keyCode is deprecated. Its not needed in case you want to set "key" property of KeyboardEvent.

How to change value of a select box in angular2 unit test?

I have an Angular2 component that contains a select box that looks like
<select [(ngModel)]="envFilter" class="form-control" name="envSelector" (ngModelChange)="onChangeFilter($event)">
<option *ngFor="let env of envs" [ngValue]="env">{{env}}</option>
</select>
I am trying to write a unit test for the ngModelChange event. This is my latest failing attempt
it("should filter and show correct items", async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
el = fixture.debugElement.query(By.name("envSelector"));
fixture.detectChanges();
makeResponse([hist2, longhist]);
comp.envFilter = 'env3';
el.triggerEventHandler('change', {});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.displayedHistory).toEqual(longhist);
});
});
The part I am having trouble with is that changing the value of the underlying model comp.envFilter = 'env3'; does not trigger the change method. I added el.triggerEventHandler('change', {}); but this throws Failed: Uncaught (in promise): ReferenceError: By is not defined. I cannot find any hints in the documentation... any ideas?
As far as the error. It seems like you just need to import By. This is not something that is global. It should be imported from the following module
import { By } from '#angular/platform-browser';
As far as the testing part, this is what I have been able to figure out. When you change a value in a the component, you need to trigger a change detection to update the view. You do this with fixture.detectChanges(). Once this is done, normally the view should be updated with the value.
From testing something similar to your example, it seems this is not the case though. It seems there is still some asynchronous task going on after the change detection. Say we have the following
const comp = fixture.componentInstance;
const select = fixture.debugElement.query(By.css('select'));
comp.selectedValue = 'a value';
fixture.DetectChanges();
expect(select.nativeElement.value).toEqual('1: a value');
This doesn't seem to work. It appears there is some async going on causing the value not to be set yet. So we need to wait for the async tasks by calling fixture.whenStable
comp.selectedValue = 'a value';
fixture.DetectChanges();
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
});
The above would work. But now we need to trigger the change event as that doesn't happen automatically.
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
// component expectations here
});
});
Now we have another asynchronous task from the event. So we need to stabilize it again
Below is a complete test that I tested with. It's a refactor of the example from the source code integration tests. They used fakeAsync and tick which is similar to using async and whenStable. But with fakeAsync, you can't use templateUrl, so I though it would be best to refactor it to use async.
Also the source code tests does kind of a double one way testing, first testing model to view, then view to model. While it looks like your test was trying to do kind of a two-way test, from model around back to model. So I refactored it a bit to suite your example better.
import { Component } from '#angular/core';
import { TestBed, getTestBed, async } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { By } from '#angular/platform-browser';
import { dispatchEvent } from '#angular/platform-browser/testing/browser_util';
#Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity" (ngModelChange)="onSelected($event)">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
onSelected(value) {
}
}
describe('component: NgModelSelectForm', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ NgModelSelectForm ]
});
});
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
});
}));
});
I found peeskillet's answer very useful but sadly it is a little out of date as the way to dispatch an Event has been changed. I also found there was an unnecessary call to whenStable(). So here is an updated test using peeskillet's setup:
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
select.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
}));
Look this example, from angular source (template_integration_spec.ts)
#Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const nycOption = fixture.debugElement.queryAll(By.css('option'))[1];
// model -> view
expect(select.nativeElement.value).toEqual('1: Object');
expect(nycOption.nativeElement.selected).toBe(true);
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
// view -> model
expect(comp.selectedCity['name']).toEqual('Buffalo');
}));
Same problem as raised by OP but slightly different code.
Works in Angular 7.
HTML:
<select id="dashboard-filter" class="form-control" name="dashboard-filter" [ngModel]="dashboardFilterValue" (ngModelChange)="onFilterChange($event)"
[disabled]="disabled">
<option *ngFor="let filter of dashboardFilters" [ngValue]="filter.value">{{ filter.name }}</option>
</select>
Unit test:
it('onFilterChange', () => {
// ensure dropdown is enabled
expect(component.disabled).toBe(false)
// spies
spyOn(component, 'onFilterChange').and.callThrough()
spyOn(component.filterChange, 'emit')
// initially the 3rd item in the dropdown is selected
const INITIAL_FILTER_INDEX = 2
// we want to select the 5th item in the dropdown
const FILTER_INDEX = 4
// the expected filter value is the value of the 5th dashboard filter (as used to populate the dropdown)
const EXPECTED_FILTER_VALUE = getDashboardFiltersData.dashboardFilters[FILTER_INDEX].value
// handle on the dropdown
const filterDropdown = fixture.debugElement.query(By.css('select')).nativeElement
// let bindings complete
fixture.whenStable().then(() => {
// ensure filterDropdown.value is stable
expect(filterDropdown.value).toContain(getDashboardFiltersData.dashboardFilters[INITIAL_FILTER_INDEX].value)
// update filterDropdown.value and dispatch change event
filterDropdown.value = filterDropdown.options[FILTER_INDEX].value
filterDropdown.dispatchEvent(new Event('change'))
// check component data
expect(component.dashboardFilterValue).toBe(EXPECTED_FILTER_VALUE)
expect(component.dashboardFilterChangeInProgress).toBe(false)
// check spies
expect(component.onFilterChange).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(setDashboardFilterSpy).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(component.filterChange.emit).toHaveBeenCalledWith(true)
})
})

Updating input html field from within an Angular 2 test

I would like to change the value of an input field from within an Angular 2 unit test.
<input type="text" class="form-control" [(ngModel)]="abc.value" />
I can't just change the ngModel because abc object is private:
private abc: Abc = new Abc();
In Angular 2 testing, can I simulate the user typing into the input field so that the ngModel will be updated with what the user has typed from within a unit test?
I can grab the DebugElement and the nativeElement of the input field without a problem. (Just setting a the value property on the nativeElement of the input field doesn't seem to work as it doesn't update the ngModel with what I've set for the value).
Maybe inputDebugEl.triggerEventHandler can be called, but I'm not sure what arguments to give it so it will simulate the user having typed a particular string of input.
You're right that you can't just set the input, you also need to dispatch the 'input' event. Here is a function I wrote earlier this evening to input text:
function sendInput(text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
Here fixture is the ComponentFixture and inputElement is the relevant HTTPInputElement from the fixture's nativeElement. This returns a promise, so you'll probably have to resolve it sendInput('whatever').then(...).
In context: https://github.com/textbook/known-for-web/blob/52c8aec4c2699c2f146a33c07786e1e32891c8b6/src/app/actor/actor.component.spec.ts#L134
Update:
We had some issues getting this to work in Angular 2.1, it didn't like creating a new Event(...), so instead we did:
import { dispatchEvent } from '#angular/platform-browser/testing/browser-util';
...
function sendInput(text: string) {
inputElement.value = text;
dispatchEvent(fixture.nativeElement, 'input');
fixture.detectChanges();
return fixture.whenStable();
}
The accepted solution didn't quite work for me in Angular 2.4. The value I had set was not appearing in the (test) UI, even after detectChanges() was called.
The way I got it to work was to set up my test as follows:
describe('TemplateComponent', function () {
let comp: TemplateComponent;
let fixture: ComponentFixture<TemplateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ TemplateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TemplateComponent);
comp = fixture.componentInstance;
});
it('should allow us to set a bound input field', fakeAsync(() => {
setInputValue('#test2', 'Tommy');
expect(comp.personName).toEqual('Tommy');
}));
// must be called from within fakeAsync due to use of tick()
function setInputValue(selector: string, value: string) {
fixture.detectChanges();
tick();
let input = fixture.debugElement.query(By.css(selector)).nativeElement;
input.value = value;
input.dispatchEvent(new Event('input'));
tick();
}
});
My TemplateComponent component has a property named personName in this example, which was the model property I am binding to in my template:
<input id="test2" type="text" [(ngModel)]="personName" />
I also had trouble getting jonrsharpe's answer to work with Angular 2.4. I found that the calls to fixture.detectChanges() and fixture.whenStable() caused the form component to reset. It seems that some initialization function is still pending when the test starts. I solved this by adding extra calls to these methods before each test. Here is a snippet of my code:
beforeEach(() => {
TestBed.configureTestingModule({
// ...etc...
});
fixture = TestBed.createComponent(LoginComponent);
comp = fixture.componentInstance;
usernameBox = fixture.debugElement.query(By.css('input[name="username"]'));
passwordBox = fixture.debugElement.query(By.css('input[type="password"]'));
loginButton = fixture.debugElement.query(By.css('.btn-primary'));
formElement = fixture.debugElement.query(By.css('form'));
});
beforeEach(async(() => {
// The magic sauce!!
// Because this is in an async wrapper it will automatically wait
// for the call to whenStable() to complete
fixture.detectChanges();
fixture.whenStable();
}));
function sendInput(inputElement: any, text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
it('should log in correctly', async(() => {
sendInput(usernameBox.nativeElement, 'User1')
.then(() => {
return sendInput(passwordBox.nativeElement, 'Password1')
}).then(() => {
formElement.triggerEventHandler('submit', null);
fixture.detectChanges();
let spinner = fixture.debugElement.query(By.css('img'));
expect(Helper.isHidden(spinner)).toBeFalsy('Spinner should be visible');
// ...etc...
});
}));