Angular Test Error - NullInjectorError: No provider for TrimInputDirective
Angular Test Error - NullInjectorError: No provider for TrimInputDirective
我创建了一个 Angular 指令,它使用 CSS 选择器在我的应用程序中自动 trim 输入,看起来是这样...
import { Directive, HostListener, forwardRef } from '@angular/core';
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export const TRIM_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrimInputDirective),
multi: true
};
/**
* The trim accessor for writing trimmed value and listening to changes that is
* used by the {@link NgModel}, {@link FormControlDirective}, and
* {@link FormControlName} directives.
*/
/* tslint:disable */
@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControl],
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[ngModel],
textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControl],
textarea
:not([readonly])
:not(.ng-trim-ignore)[ngModel],
:not([readonly])
:not(.ng-trim-ignore)
[ngDefaultControl]'
`,
providers: [ TRIM_VALUE_ACCESSOR ]
})
/* tslint:enable */
export class TrimInputDirective extends DefaultValueAccessor {
protected _onTouched: any;
/**
* ngOnChange - Lifecycle hook that is called when any data-bound property of a directive changes.
* @param {string} val - trim value onChange.
*/
@HostListener('input', ['$event.target.value'])
public ngOnChange = (val: string) => {
this.onChange(val.trim());
}
/**
* applyTrim - trims the passed value
* @param {string} val - passed value.
*/
@HostListener('blur', ['$event.target.value'])
public applyTrim(val: string) {
this.writeValue(val.trim());
this._onTouched();
}
/**
* writeValue - trims the passed value
* @param {any} value - passed value.
*/
public writeValue(value: any): void {
if (typeof value === 'string') {
value = value.trim();
}
super.writeValue(value);
}
/**
* registerOnTouched Registers a callback function that should be called when the control receives a blur event.
* @param {function} fn - The user information.
*/
public registerOnTouched(fn: any): void {
this._onTouched = fn;
}
}
现在作为一名优秀的开发人员,我必须正确进行一些单元测试...所以我开始整理一个文件,就在这里
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {TrimInputDirective} from './trim-input.directive';
import {expect} from 'chai';
@Component({
selector: 'my-directive-test-component',
template: ''
})
class TestComponent {
}
describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type="text">'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
inputDebugElement = fixture.debugElement.query(By.css('input'));
directive = inputDebugElement.injector.get(TrimInputDirective);
});
}));
it('should trim the input', () => {
directive.ngOnChange(' 1234.56 ')
expect('1234.56').to.be('1234.56'); // I know this isn't the correct test... I will amend this
});
});
现在我希望 运行 我的测试只是为了确保规范文件中的设置是正确的,但是我收到以下错误:
HeadlessChrome 0.0.0 (Mac OS X 10.12.6) Trim Directive "before each"
hook for "should trim the input" FAILED Uncaught (in promise): Error:
StaticInjectorError(DynamicTestModule)[TrimInputDirective]:
StaticInjectorError(Platform: core)[TrimInputDirective]:
NullInjectorError: No provider for TrimInputDirective! Error: StaticInjectorError(DynamicTestModule)[TrimInputDirective]:
我不明白为什么会出现这个错误,为什么我必须提供指令?我不认为这是必要的,如果我必须提供我提供什么?提供实际指令不起作用/解决错误?我很迷茫。如果有人能告诉我如何解决这个问题或者我为什么会遇到这个问题,我将不胜感激。
请注意,这是旧版 Angular 应用程序,是在
AngularCLI 可用。所以它有点不正统(例如它没有使用 Jasmin)。
您必须在 TestComponent
中提供类似这样的指令
@Component({
selector: 'my-directive-test-component',
template: '',
providers: [ TrimInputDirective ]
})
class TestComponent {
}
1) 你不需要提供你的指令,你只需要在TestingModule
中声明它。然后,它将在模板中与相应的选择器一起使用。
2) 您的选择器与您输入的选择器不一致。如果您希望它应用于某些类型的所有输入或更改您的测试,请删除 formControlName
。
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
^^^^^^^^^^^^^^^^^^
3) 指令在某些事件上被触发。您需要模拟这些事件才能看到效果。看看这个简化的例子。 (Stackblitz)
@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
`
})
export class TrimInputDirective {
constructor(private el: ElementRef) { }
@HostListener('blur') onLeave() {
if (this.el.nativeElement.value)
this.el.nativeElement.value = this.el.nativeElement.value.trim();
}
}
测试:
describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
imports: [FormsModule],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type="text">'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
inputDebugElement = fixture.debugElement.query(By.css('input')).nativeElement;
^^^^^^^^^^^^
});
}));
it('should trim the input', () => {
inputDebugElement.value = ' 1234.56 ';
inputDebugElement.dispatchEvent(new Event('blur'));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fixture.detectChanges();
expect(inputDebugElement.value).toBe('1234.56');
});
});
我创建了一个 Angular 指令,它使用 CSS 选择器在我的应用程序中自动 trim 输入,看起来是这样...
import { Directive, HostListener, forwardRef } from '@angular/core';
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export const TRIM_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TrimInputDirective),
multi: true
};
/**
* The trim accessor for writing trimmed value and listening to changes that is
* used by the {@link NgModel}, {@link FormControlDirective}, and
* {@link FormControlName} directives.
*/
/* tslint:disable */
@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControl],
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[ngModel],
textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
textarea
:not([readonly])
:not(.ng-trim-ignore)
[formControl],
textarea
:not([readonly])
:not(.ng-trim-ignore)[ngModel],
:not([readonly])
:not(.ng-trim-ignore)
[ngDefaultControl]'
`,
providers: [ TRIM_VALUE_ACCESSOR ]
})
/* tslint:enable */
export class TrimInputDirective extends DefaultValueAccessor {
protected _onTouched: any;
/**
* ngOnChange - Lifecycle hook that is called when any data-bound property of a directive changes.
* @param {string} val - trim value onChange.
*/
@HostListener('input', ['$event.target.value'])
public ngOnChange = (val: string) => {
this.onChange(val.trim());
}
/**
* applyTrim - trims the passed value
* @param {string} val - passed value.
*/
@HostListener('blur', ['$event.target.value'])
public applyTrim(val: string) {
this.writeValue(val.trim());
this._onTouched();
}
/**
* writeValue - trims the passed value
* @param {any} value - passed value.
*/
public writeValue(value: any): void {
if (typeof value === 'string') {
value = value.trim();
}
super.writeValue(value);
}
/**
* registerOnTouched Registers a callback function that should be called when the control receives a blur event.
* @param {function} fn - The user information.
*/
public registerOnTouched(fn: any): void {
this._onTouched = fn;
}
}
现在作为一名优秀的开发人员,我必须正确进行一些单元测试...所以我开始整理一个文件,就在这里
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {TrimInputDirective} from './trim-input.directive';
import {expect} from 'chai';
@Component({
selector: 'my-directive-test-component',
template: ''
})
class TestComponent {
}
describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type="text">'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
inputDebugElement = fixture.debugElement.query(By.css('input'));
directive = inputDebugElement.injector.get(TrimInputDirective);
});
}));
it('should trim the input', () => {
directive.ngOnChange(' 1234.56 ')
expect('1234.56').to.be('1234.56'); // I know this isn't the correct test... I will amend this
});
});
现在我希望 运行 我的测试只是为了确保规范文件中的设置是正确的,但是我收到以下错误:
HeadlessChrome 0.0.0 (Mac OS X 10.12.6) Trim Directive "before each" hook for "should trim the input" FAILED Uncaught (in promise): Error: StaticInjectorError(DynamicTestModule)[TrimInputDirective]:
StaticInjectorError(Platform: core)[TrimInputDirective]: NullInjectorError: No provider for TrimInputDirective! Error: StaticInjectorError(DynamicTestModule)[TrimInputDirective]:
我不明白为什么会出现这个错误,为什么我必须提供指令?我不认为这是必要的,如果我必须提供我提供什么?提供实际指令不起作用/解决错误?我很迷茫。如果有人能告诉我如何解决这个问题或者我为什么会遇到这个问题,我将不胜感激。
请注意,这是旧版 Angular 应用程序,是在 AngularCLI 可用。所以它有点不正统(例如它没有使用 Jasmin)。
您必须在 TestComponent
中提供类似这样的指令
@Component({
selector: 'my-directive-test-component',
template: '',
providers: [ TrimInputDirective ]
})
class TestComponent {
}
1) 你不需要提供你的指令,你只需要在TestingModule
中声明它。然后,它将在模板中与相应的选择器一起使用。
2) 您的选择器与您输入的选择器不一致。如果您希望它应用于某些类型的所有输入或更改您的测试,请删除 formControlName
。
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
[formControlName],
^^^^^^^^^^^^^^^^^^
3) 指令在某些事件上被触发。您需要模拟这些事件才能看到效果。看看这个简化的例子。 (Stackblitz)
@Directive({
selector: `
input
:not([type=checkbox])
:not([type=radio])
:not([type=password])
:not([readonly])
:not(.ng-trim-ignore)
`
})
export class TrimInputDirective {
constructor(private el: ElementRef) { }
@HostListener('blur') onLeave() {
if (this.el.nativeElement.value)
this.el.nativeElement.value = this.el.nativeElement.value.trim();
}
}
测试:
describe('Trim Directive', () => {
let fixture: ComponentFixture<TestComponent>;
let inputDebugElement: any;
let directive: TrimInputDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestComponent,
TrimInputDirective
],
imports: [FormsModule],
providers: []
}).overrideComponent(TestComponent, {
set: {
template: '<input type="text">'
}
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TestComponent);
inputDebugElement = fixture.debugElement.query(By.css('input')).nativeElement;
^^^^^^^^^^^^
});
}));
it('should trim the input', () => {
inputDebugElement.value = ' 1234.56 ';
inputDebugElement.dispatchEvent(new Event('blur'));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fixture.detectChanges();
expect(inputDebugElement.value).toBe('1234.56');
});
});