0

I am testing this html form:

 <input #nhcInput type="text" class="form-control" name="nhc" id="field_nhc"
	                [(ngModel)]="paciente.nhc" maxlength="38" pattern="[0-9]+"/>
	            <div [hidden]="!(editForm.controls.nhc?.dirty && editForm.controls.nhc?.invalid)">
	                <small class="form-text text-danger"
	                   [hidden]="!editForm.controls.nhc?.errors?.maxlength" jhiTranslate="entity.validation.maxlength" translateValues="{ max: 38 }">
	                   This field cannot be longer than 38 characters.
	                </small>
	               

TEH RESULT {{nhcInput.className}} //This line prints ng-valid/ dirty, touched correctly

I have this in my component:

    paciente: Paciente = {nhc: '23423' } as Paciente;

  it ('NHC cannot have more than 38 characters', async(() => {
               
                      comp.paciente.nhc = 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr' ;             
                      console.log(fixture.nativeElement.querySelector('input[name="nhc"]').className);
                      fixture.detectChanges();              
                     expect(fixture.nativeElement.querySelector('input[name="nhc"]').className.includes('ng-invalid')).toEqual(true);
                      })); 

Now I want to test the validity by checking the vaidator. The console.log prints out only form-control without the type of validator, since it is not finding it.

I put a validator for this fild like this in my component:

@ViewChild('editForm') editForm: any;
 editform.controls["nhc"].setValidators([ Validators.maxLength(38)]);

But this doesnt work. Am I doing soemthing wrong here?

Thanks!

1 Answer 1

1

Your issue comes from the fact that you don't do things in order and don't rely on the framework to make your tests.

I made a sandbox with a working test, feel free to look at it. Now for the explanation :

Let's start with the component :

@Component({
  selector: 'hello',
  template: `
  <form #myForm="ngForm">
    <input type="text" maxlength="20" name="patient" id="patient" [(ngModel)]="patient">
  </form>
  `,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent  {
  @ViewChild('myForm') myForm: NgForm;
  patient: string;
}

A very simple component with a template driven form and a basic validation and binding.

If you do this

ngOnInit() {
  console.log(this.myForm.controls);
  setTimeout(() => console.log(this.myForm.controls));
}

You will see both undefined and { patient: FormControl }. This happens because you don't wait for the view to be initialized before doing your tests. This means that the test can't find the form control, and hence can't pass.

Now for the test itself :

import { Component } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { HelloComponent } from './hello.component';

import { FormsModule } from '@angular/forms';

describe('HelloComponent', () => {
  let fixture: ComponentFixture<HelloComponent>;
  let component: HelloComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [FormsModule],
      declarations: [HelloComponent]
    });

    fixture = TestBed.createComponent(HelloComponent);
    component = fixture.debugElement.children[0].componentInstance;

    fixture.detectChanges();
  });

  it('should be invalid with more than 20 chars', async(() => {
    setTimeout(() => {
      component.myForm.controls['patient'].setValue('ddddddddddddddddddddd'); // 21
      fixture.detectChanges();
      expect(component.myForm.control.invalid).toEqual(true);
    });
  }));
});

the start is very basic, test bed is configured and changes are detected.

Now comes the part where you test : you first must wait for the component to load with a timeout, then, you need to set the value on the form by using the framework :

component.myForm.controls['patient'].setValue('ddddddddddddddddddddd'); // 21

This inputs 21 d into the input, which is making it invalid. After that, you need to trigger the changes detection, and now you can make your expectation with

expect(component.myForm.control.invalid).toEqual(true);

This will take the form as a control, meaning it has all the properties and functions a FormControl has. Among these, you can find the invalid property, which states if your control is in an invalid state.

Again, I have to state that this kind of test is useless, because you basically try to see if the Framework is working properly. That's not your job, that's Angular team's job. If you want to test something, you should test that the form can't be submitted when it has an invalid state, which is a business rule (I guess) and prevents side effects (unlike this test).

Sign up to request clarification or add additional context in comments.

15 Comments

But for the purpose of the test, I ma testing if it outputs the errors in the case of something invalid. And since teh submitt button is disabled on invalid datas this means it cannot sumbit invalid values
And cant I use validators to output the className of the element?
Again, this kind of test is useless, Angular will display the error message and hide the buttons given your conditions. You don't need to test if Angular works, only if your business logic is coping with your expectations. And instead of testing the className, you should directly test if the element is display through a ViewChild reference.
component.myForm.controls['paciente.nhc'].setValue it says cannot take setValue of undefined. Inside controls can I put teh reference of teh viewChld of teh nhc? And what type should it be ElementRef?
Why did we move from this expect(fixture.nativeElement.querySelector('input[name="apellidos"]').className.includes('ng-invalid')).toEqual(true); to this new type of test. I like more teh one with class Name cause you can test pristine and dirty too
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.