1

Problem

I have a container component that uses a custom input component. It works fine while serving or after building, but, it doesn't work on tests.

This is the error I'm getting: Error: No value accessor for form control with name: 'name'


Here is a simplified version of my files:

Template

<app-modal>
  <form [formGroup]="myForm" novalidate>
    <app-input formControlName="name"></app-input>
  </form>
</app-modal>

Test

@Component({ selector: 'app-input', template: '' })
class InputStubComponent {}

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

I've also tried: using CUSTOM_ELEMENTS_SCHEMA and not declaring the component.

1 Answer 1

4

There are two ways of solving this:

RECOMMENDED
1) You can remove the ReactiveFormsModule from the imports property, and add the FormBuilder to the providers property. The ReactiveFormsModule adds formGroup and formControlName directives, which do checks on a template level.

This is how it would look like in your example:

beforeEach(async(() => {
  TestBed.configureTestingModule({
    schemas: [ NO_ERRORS_SCHEMA ],
    declarations: [
      InputStubComponent
    ],
    providers: [
      FormBuilder
    ]
  })
  .compileComponents();
}));

2) If you want to test your child component, or to keep the ReactiveFormsModule, here is a function that creates a barebones stub component with value accessor:

const createAccessorComponent = (selector) => {
  @Component({
    selector: selector,
    template: '',
    providers: [
      {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => AccessorComponent),
        multi: true,
      },
    ],
  })
  class AccessorComponent implements ControlValueAccessor {
    writeValue(obj: any): void {}
    registerOnChange(fn: any): void {}
    registerOnTouched(fn: any): void {}
    setDisabledState(isDisabled: boolean): void {}
  }

  return AccessorComponent;
};

You can use it in this way:

const InputStubComponent = createAccessorComponent('app-input');

beforeEach(async(() => {
  TestBed.configureTestingModule({
    schemas: [ NO_ERRORS_SCHEMA ],
    declarations: [
      InputStubComponent
    ],
    imports: [
      ReactiveFormsModule,
    ]
  })
  .compileComponents();
}));
Sign up to request clarification or add additional context in comments.

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.