Angular2 how to unit test a custom validator directive?
Asked Answered
S

1

27

I wrote a very simple custom validator for an input field:

import { Directive } from '@angular/core';
import { AbstractControl, NG_VALIDATORS } from '@angular/forms';

function numberValidator(c: AbstractControl) {
    if (!c.value) return null;
    return new RegExp('^[1-9][0-9]{6,9}$').test(c.value) ? null : {
        validateNumber: {
            valid: false
        }
    }
}

@Directive({
    selector: '[number-validator]',
    providers: [
        { provide: NG_VALIDATORS, multi: true, useValue: numberValidator }
    ]
})
export class NumberValidator {}

I would like to unit test this validator. I read Test an attribute directive on the Angular2 page, but there is no css or html that changes. How can I unit test this validator?

Spend answered 10/10, 2016 at 11:59 Comment(0)
W
43

If you want to do it the easy way (which I would do, since all the logic is in the validator function), is just to test the validator function. Just pass a control to it

expect(numberValidator(new FormControl('123456'))).toEqual({
  'validateNumber': { 'valid': false }
});
expect(numberValidator(new FormControl('123456789'))).toEqual(null);

If you really want to test it when "being used", then it gets a little tedious. These are usually the steps I take

  1. Create dummy component to use the directive
  2. Set up the test bed configuration
  3. Create the component to test.
  4. Get the native input element and dispatch an invalid input event to it
  5. Get the injector that holds the NgForm
  6. Check the form for failure
  7. Put a valid input and check that it passes.

It's a lot compared to just testing the validator method. But here it is anyway ;-) Enjoy!

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

function numberValidator(c: AbstractControl) {
  if (!c.value) return null;
  return new RegExp('^[1-9][0-9]{6,9}$').test(c.value) ? null : {
    validateNumber: {
      valid: false
    }
  };
}

@Directive({
  selector: '[number-validator]',
  providers: [
    { provide: NG_VALIDATORS, multi: true, useValue: numberValidator }
  ]
})
export class NumberValidator {
}

@Component({
  template: `
    <form>
      <input name="number" type="text" ngModel number-validator />
    </form>
  `
})
class TestComponent {
}

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

  it('should validate (easy)', () => {
    expect(numberValidator(new FormControl('123'))).toEqual({
      'validateNumber': { 'valid': false }
    });
    expect(numberValidator(new FormControl('123456789'))).toEqual(null);
  });

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

    fixture.detectChanges();
    fixture.whenStable().then(() => {
      input.nativeElement.value = '123';
      dispatchEvent(input.nativeElement, 'input');
      fixture.detectChanges();

      let form: NgForm = debug.children[0].injector.get(NgForm);
      let control = form.control.get('number');

      // just to show a few different ways we can check validity
      expect(control.hasError('validateNumber')).toBe(true);
      expect(control.valid).toBe(false);
      expect(form.control.valid).toEqual(false);
      expect(form.control.hasError('validateNumber', ['number'])).toEqual(true);

      input.nativeElement.value = '123456789';
      dispatchEvent(input.nativeElement, 'input');
      fixture.detectChanges();

      expect(form.control.valid).toEqual(true);
    });
  }));
});
Wolfort answered 10/10, 2016 at 13:13 Comment(3)
When I try the second approach. I am getting no provider found for ngForm. I am sure that should be a mistake on my part. Do you know what could be this issue?Aphis
A better way to get the NgForm is to use the debug.query(By.directive(NgForm)). I was just looking at the angular source code tests to find the way that I did it in the above post. But using By.directive is probably the better way.Wolfort
WOW!!! Answers like this is what .makes SO super great! Thank you! 😊Instructions

© 2022 - 2024 — McMap. All rights reserved.