Updating input html field from within an Angular 2 test
Solution 1
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(...)
.
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();
}
Solution 2
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" />
Solution 3
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...
});
}));
Daniel
Updated on July 21, 2022Comments
-
Daniel almost 2 years
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
becauseabc
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 thenativeElement
of the input field without a problem. (Just setting a thevalue
property on thenativeElement
of the input field doesn't seem to work as it doesn't update thengModel
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.