why ngForm "controls" property is empty in my test? (Angular)
Asked Answered
R

1

6

I have a component which employs template-driven form

<form (ngSubmit)="onSearchCorpus(form)" #form="ngForm">
<combo-box [(ngModel)]="model.corpus" name="corpus" #corpus="ngModel"></combo-box>
<input [(ngModel)]="model.label" name="label" id="label" type="text" required pattern="^(?!\s*$)[\w\_\-\:\s]*" maxlength="50" class="form-control label-name" autocomplete="off" #label="ngModel">
<textarea [(ngModel)]="model.query" name="query" id="query" maxlength="3600" required validateQuerySyntax class="form-control search-query" #query="ngModel" #queryControl
        placeholder="Example: (&quot;grantee&quot; OR &quot;grant&quot; OR &quot;sponsor&quot; OR &quot;contribute&quot; OR &quot;contributor&quot;) NEAR (&quot;non-profit organization&quot; OR &quot;charities&quot;)">
      </textarea>
 <button [disabled]="corpusValidationInProgress" type="submit" class="button-level-one">Search</button>
</form>

In the method that handles form submission I access controls property of NgForm instance and it works fine in browser.

onSearchCorpus(formData: NgForm) {
   ...
   const corpusErrors = formData.controls.corpus.errors;
   ...
}

However when I try to test this method with Karma the NgForm's controls property is empty. I'm confused why is that. The method fails with error cannot read property "errors" of undefined.

Here is how my test looks like:

it('should not perform corpusSearch if selected corpus no longer exists', () => {
    component.ngOnInit();
    const form = fixture.debugElement.query(By.css('form'));
    form.triggerEventHandler('submit', null);
    ...
});

and this is how I'm setting up my test suit:

beforeEach(async(() => {
    TestBed.configureTestingModule({
        // schemas: [NO_ERRORS_SCHEMA],
        imports: [
            FormsModule,
            PopoverModule
        ],
        providers: [
            CorpusSearchService,
            { provide: ApiService, useValue: ApiServiceStub },
            { provide: Router, useClass: RouterStab },
        ],
        declarations: [
            SearchCorpusComponent, //<--component under test
            ComboBoxComponent //<-- 3rd party combobox which is used for first control 
        ]
    }).compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(SearchCorpusComponent);
    component = fixture.componentInstance;
    apiService = fixture.debugElement.injector.get(ApiService);
    subject = new Subject();
});

So, why controls is empty in test environment?

Rambling answered 16/10, 2017 at 12:6 Comment(0)
L
11

The reason why controls is "empty" is that the controls were not initialized "yet" in the test environment at the time of calling onSearchCorpus.

I've prepared a Plunkr (see form.component.spec file) which demonstrates this issue. In the same file there is also a working solution.

So in short, in order to make this work you need to use fakeAsync mechanism.

So instead of:

  it('testing form the wrong way', () => {

    fixture.detectChanges();

    comp.onSubmit(); // Validates and submits a form

    expect(comp.submitted).toEqual(false);
  });

you should write a test like:

  it('testing form the proper way', fakeAsync(() => {

    // This first detectChanges is necessary to properly set up the form
    fixture.detectChanges();

    // Tick needs to be called in order for form controls to be registered properly.
    tick();

    comp.onSubmit(); // Validates and submits a form

    expect(comp.submitted).toEqual(false);

  }));
Lazurite answered 26/10, 2017 at 9:22 Comment(1)
It would be great if Angular's docs had at least a whisper about needing to do this.Jehoshaphat

© 2022 - 2024 — McMap. All rights reserved.