75

I have been reading official Angular2 documentation for unit testing (https://angular.io/docs/ts/latest/guide/testing.html) but I am struggling with setting a component's input field value so that its reflected in the component property (bound via ngModel). The screen works fine in the browser, but in the unit test I cannot seem to be able to set the fields value.

I am using below code. "fixture" is properly initialized as other tests are working fine. "comp" is instance of my component, and the input field is bound to "user.username" via ngModel.

it('should update model...', async(() => {
    let field: HTMLInputElement = fixture.debugElement.query(By.css('#user')).nativeElement;
    field.value = 'someValue'
    field.dispatchEvent(new Event('input'));
    fixture.detectChanges();

    expect(field.textContent).toBe('someValue');
    expect(comp.user.username).toBe('someValue');
  }));

My version of Angular2: "@angular/core": "2.0.0"

isherwood
  • 58,414
  • 16
  • 114
  • 157
Zyga
  • 2,367
  • 3
  • 22
  • 32

4 Answers4

118

Inputs don't have textContent, only a value. So expect(field.textContent).toBe('someValue'); is useless. That's probably what's failing. The second expectation should pass though. Here's a complete test.

@Component({
  template: `<input type="text" [(ngModel)]="user.username"/>`
})
class TestComponent {
  user = { username: 'peeskillet' };
}

describe('component: TestComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [FormsModule],
      declarations: [ TestComponent ]
    });
  });

  it('should be ok', async(() => {
    let fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();
    fixture.whenStable().then(() => {
      let input = fixture.debugElement.query(By.css('input'));
      let el = input.nativeElement;

      expect(el.value).toBe('peeskillet');

      el.value = 'someValue';
      el.dispatchEvent(new Event('input'));

      expect(fixture.componentInstance.user.username).toBe('someValue');
    });
  }));
});

The important part is the first fixture.whenStable(). There is some asynchronous setup with the forms that occurs, so we need to wait for that to finish after we do fixture.detectChanges(). If you are using fakeAsync() instead of async(), then you would just call tick() after fixture.detectChanges().

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • mine wasnt working even without the first 'expect' actually. But yours is working fine thankfully, no idea why tho, I assume its because its after whenStable() is resolved? Thanks for the help anyway. – Zyga Dec 09 '16 at 16:23
  • 2
    If you were setting up the component in the beforeEach, making the beforeEach async, then calling fixture.detectChanges after creating the component, would probably work with the code above without the need to call whenStable. If you did this with the code above you can get rid of everything except for what's in the then callback. I think that would work also. The async beforeEach should pretty much do the same thing that whenStable would do, which is wait for the async tasks to complete before exiting the beforeEach – Paul Samsotha Dec 09 '16 at 16:42
  • 5
    `fixture.whenStable().then(() => {` - was the missing bit for me thanks. – Kieran Apr 26 '18 at 12:49
  • 11
    ...`peeskillet?` – Jacob Stamm Jul 11 '18 at 20:57
  • @JacobStamm yeah? That was my SO username before I changed it to my real name. – Paul Samsotha Jul 11 '18 at 21:07
  • In case you wrongly use el.dispatchEvent (or anything else), the test will still pass but throw inside the `then` closure. You can add `.catch( e => { expect(e).toBeFalsy(); });` to make the test fail in case you do something like `el.dispatchEvent('blur');`. – Ambroise Rabier Apr 17 '19 at 09:21
  • I was using the input as a filter for a table, in order to test the table had the correct filtered results, I also had to use `whenStable()`, thanks! – andy mccullough Jul 29 '19 at 13:26
21

Just add

fixture.detectChanges();

fixture.whenStable().then(() => {
  // here your expectation
})
ktretyak
  • 27,251
  • 11
  • 40
  • 63
  • 1
    Is it fair to say it's a good idea to always use `fixture.whenStable()` in every test? Like as a noob is it a good idea or only use in certain cases? – Adam Hughes Jan 20 '22 at 22:43
6

Use your expect/assert within the whenStable.then function like this:

component.label = 'blah';
fixture.detectChanges();

fixture.whenStable().then(() => {
    expect(component.label).toBe('blah');
}
Dumbo
  • 1,630
  • 18
  • 33
Akash Yellappa
  • 2,126
  • 28
  • 21
0

If you want to implement Unit Testing with @Input content then go through this below code.

testing.component.ts

    import { Component, OnInit } from '@angular/core';
    @Component({
      selector: 'app-testing',
      templateUrl: `<app-input [message]='text'></app-input>`,
      styleUrls: ['./testing.component.css']
    })
    export class TestingComponent implements OnInit {
    public text = 'input message';
      constructor() { }
    
      ngOnInit() {
      }
    
    }

input.component.ts

    import { Component, OnInit, Input } from '@angular/core';
    
    @Component({
      selector: 'app-input',
      templateUrl: `<div *ngIf="message">{{message}}</div>`,
      styleUrls: ['./input.component.css']
    })
    export class InputComponent implements OnInit {
    @Input() public message: string;
      constructor() { }
    
      ngOnInit() {
        console.log(this.message);
      }
    
    }

input.component.spec.ts

    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { InputComponent } from './input.component';
    import { TestingComponent } from '../testing/testing.component';
    
    describe('InputComponent', () => {
      let component: InputComponent;
      let fixture: ComponentFixture<InputComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ InputComponent, TestingComponent ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(InputComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    
      it('should correctly render the passed @Input value', () => {
        component.message = 'test input';
        fixture.detectChanges();
        expect(fixture.nativeElement.innerText).toBe('test input');
      });
    
    });
James Jenkinson
  • 1,563
  • 2
  • 16
  • 33
Priti jha
  • 145
  • 1
  • 4