Angular 2 Custom validation unit testing
Asked Answered
G

3

6

i'm writing custom angular(Angular 2.0.0) validation, following this guide https://angular.io/docs/ts/latest/cookbook/form-validation.html#!#custom-validation .

@Directive({
  selector: '[ngModel][emailValidator]',
  providers: [{provide: NG_VALIDATORS, useExisting: EmailValidatorDirective, multi: true}]
})
export class EmailValidatorDirective implements Validator 

Now i'm trying to add unit test to my custom validation directive.

beforeEach(() => {
      fixture = TestBed.createComponent(EmailComponent);
      component = fixture.componentInstance;
      de = fixture.debugElement;
      el = de.nativeElement;
      component = de.componentInstance;
      emailField = de.query(By.css('input')).nativeElement;
    });

I'm accessing all these object, but no one has any information about the validity of my input. Does anyone has any idea how to access the NgControl of my input inside the Unit Tests, or how can i check for valid/invalid(custom validation) input field.

Grating answered 7/10, 2016 at 5:25 Comment(0)
T
13

What you need to do is get the injector that has the NgForm. It took me a while to figure it out. I thought you could just get it from the debugElement, but it looks like you need to get it from it's child1.

let form: NgForm = fixture.debugElement.children[0].injector.get(NgForm);

The you can just get individual controls from the form group with

let emailControl = form.control.get('email');
expect(emailControl.valid).toBe(true);

Or you can just check the form for a specific error

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

Below is a complete test

import { Component, forwardRef, Directive } from '@angular/core';
import { TestBed, getTestBed, async } from '@angular/core/testing';
import { FormsModule, NG_VALIDATORS, Validator, AbstractControl, NgForm } from '@angular/forms';
import { dispatchEvent } from '@angular/platform-browser/testing/browser_util';
import { By } from '@angular/platform-browser';

@Directive({
  selector: '[ngModel][validEmail]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => EmailValidatorDirective),
      multi: true
    }
  ]
})
class EmailValidatorDirective implements Validator {

  validate(c: AbstractControl): { [key: string]: any } {
    if (c.value !== '[email protected]') {
      return { notPeeskillet: true };
    }
    return null;
  }
}

@Component({
  template: `
    <form>
      <input name="email" [ngModel]="email" validEmail />
    </form>
  `
})
class TestComponent {
  email;
}

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

  it('should validate', async(() => {
    let fixture = TestBed.createComponent(TestComponent);
    let comp = fixture.componentInstance;
    let debug = fixture.debugElement;
    let input = debug.query(By.css('[name=email]'));

    fixture.detectChanges();
    fixture.whenStable().then(() => {
      input.nativeElement.value = '[email protected]';
      dispatchEvent(input.nativeElement, 'input');
      fixture.detectChanges();

      let form: NgForm = debug.children[0].injector.get(NgForm);
      let control = form.control.get('email');
      expect(control.hasError('notPeeskillet')).toBe(true);
      expect(form.control.valid).toEqual(false);
      expect(form.control.hasError('notPeeskillet', ['email'])).toEqual(true);

      input.nativeElement.value = '[email protected]';
      dispatchEvent(input.nativeElement, 'input');
      fixture.detectChanges();

      expect(control.hasError('notPeeskillet')).toBe(false);
      expect(form.control.valid).toEqual(true);
      expect(form.control.hasError('notPeeskillet', ['email'])).toEqual(false);
    });
  }));
});

1 - Found it in the source code tests

Ta answered 7/10, 2016 at 13:44 Comment(2)
browser_util has moved to import { dispatchEvent } from '@angular/platform-browser/testing/src/browser_util';Owlet
Use the built-in dispatch event insteadArms
E
2

The above best answer didn't work for me but I was able to test my custom validator in the following way:

const associateRateControl = component.distributionSettingsForm.controls['associateRate'];
associateRateControl.setValue('plus ultra');
fixture.detectChanges();
expect(component.distributionSettingsForm.hasError('noCatalog')).toEqual(true);
Elvyn answered 16/5, 2018 at 18:30 Comment(0)
M
0

Another heads-up on that topic. Paul Samsotha answer I think was correct one and very helpful, but TBH you don't need to import any library. We can use the nativeElement dispatchEvent and make our lives a little bit easier.

beforeEach(() => {
    const input = fixture.debugElement.query(By.css("input")).nativeElement;
    fixture.detectChanges();
    input.value = "test";
    input.dispatchEvent(new Event("input"));
    fixture.detectChanges();
});

it("should display the password strength indicator", () => {
    const form = fixture.componentInstance.myMarvelousForm;
    expect(form.dirty).toBeTruthy();
});
Mahlstick answered 21/8, 2020 at 9:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.