18

I'm learning Angular 2 and unit testing using @angular/cli 1.0.0-beta.30 and had a bit of success testing one aspect of a form field's validity, but not all. I'm using an inline template in my component for the time being to remove a layer of complexity for now (a form template in a separate file introduces asynchronicity, correct?).

The ngOnInit() defines a name property that includes the validators for "required" and "minLength". Currently an empty form field will correctly trigger the "required" validator but not the "minLength" validator. The name.errors array in the test does not contain any reference to required at all, name.errors['minLength'] returns undefined. Does minLength need to be handled asynchronously? I'm having trouble finding docs or examples that fit my problem.

// signup-form.component.ts
...
export class SignupFormComponent implements OnInit {

    user: FormGroup;

    constructor(private fb: FormBuilder) {
    }

    ngOnInit() {
        this.user = this.fb.group({
            name: ['', [Validators.required, Validators.minLength(2)]],
            account: this.fb.group({
                email: ['', Validators.required, Validators.pattern("[^ @]*@[^ @]*")],
                confirm: ['', Validators.required]
            })
        })
    }

    onSubmit({ value, valid }: { value: User, valid: boolean }) {
        console.log(value, valid);
    }

}

My test

// signup-form.component.spec.ts
import { SignupFormComponent } from './signup-form.component';

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

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [SignupFormComponent],
            imports: [
                ReactiveFormsModule,
                FormsModule
            ]
        })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(SignupFormComponent);
        component = fixture.componentInstance;
        component.ngOnInit();
        fixture.detectChanges();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });

    it('form invalid when empty', () => {
        expect(component.user.valid).toBeFalsy();
    });

    it('name field validity', () => {
        let name = component.user.controls['name'];
        expect(name.valid).toBeFalsy();

        let errors = {};
        name.setValue("");
        errors = name.errors || {};
        expect(errors['required']).toBeTruthy(); // this works
        expect(errors['minLength']).toBeTruthy(); // this fails, "undefined"
    });

});

6 Answers 6

20

Answering your question

The name.errors['minLength'] returns undefined, Does minLength need to be handled asynchronously?

You can just check the form for a specific error

expect(form.control.hasError('emailInvalid', ['email'])).toBe(true);

Below is a complete test

// signup-form.component.spec.ts
import { SignupFormComponent } from './signup-form.component';

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

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [SignupFormComponent],
            imports: [
                ReactiveFormsModule,
                FormsModule
            ]
        })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(SignupFormComponent);
        component = fixture.componentInstance;
        component.ngOnInit();
        fixture.detectChanges();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });

    it('form invalid when empty', () => {
        expect(component.user.valid).toBeFalsy();
    });

    it('name field validity', () => {
        let name = component.user.controls['name'];
        expect(name.valid).toBeFalsy();

        name.setValue("");
        expect(name.hasError('required')).toBeTruthy();

        name.setValue("A");
        expect(name.hasError('minLength')).toBeTruthy();
    });

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

Comments

3
expect(name.hasError('minlength', ['minlength'])).toEqual(false);

Try this it worked for me

Comments

1

I believe it's because the validation key you're looking for is "minlength" non-camelCase, note the lower-case "l". I have personally run into this oddly named error property.

Comments

0
expect(errors['minlength']).toBeTruthy();

Comments

0

'minLength' is handled in the same way as 'required' or any of the other Validators (e.g. maxLength, pattern).

Unless your signup-form.component.ts code is truncated, I believe that you are missing a couple of things.

Inside your FormBuilder, you need to subscribe to data changes. this.user.valueChanges.subscribe(data => this.onValueChanged(data));

Then you need to declare the onValueChanged function. Consult the Angular Form Validation cookbook for a generic onValueChanged function.

You'll also need to populate the formErrors object with the appropriate keys.

formErrors = {
  'name': ''
 }

And supply validationMessages.

validationMessages = {
  'name': {
    'required': 'Name is required.',
    'minlength': 'Name must be at least 2 characters long.'
  }
 }

Comments

0

It is due to Validator.required priority, when such error exists - other validators are not even invoked and does not produce other errors. You need to set some incorrect value for testing that specific part :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.