如何测试表单控制避免括号符号以保持类型检查和可维护性

发布于 2025-01-28 13:30:05 字数 3140 浏览 4 评论 0 原文

当我重构对象的成员时,我的设备测试在形式中断时进行了测试

,因为我只能使用括号符号访问集体上的FormControls。基于字符串的测试成员的选择不会被任何IDE重构。

这是一个示例...

对象:

class Task {
  constructor(
    public id: number = 0,
    public description: string = '',    // <--- member under test
    public completed: boolean = false,
    public priority: Priority = Priority.Normal
  ) {}

  clone(): Task {
    return new Task(this.id, this.description, this.completed, this.priority);
  }
}

组件:

export class AppComponent implements OnInit {
  form!: FormGroup;
  task: Task;
  priorities = [Priority.Low, Priority.Normal, Priority.High];

  constructor(private fb: FormBuilder) {
    this.task = this.mockTask();
  }

  ngOnInit() {
    this.task = this.mockTask();
    this.form = this.fb.group({
      description: [
        this.task.description,
        Validators.compose([Validators.required, Validators.maxLength(50)]),
      ],
      priority: [this.task.priority],
    });
  }

  // ...
}

测试:

describe('AppComponent', () => {
  let comp: AppComponent
  let fixture: ComponentFixture<AppComponent>;
  let task: Task;
  let taskService: TaskService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      declarations: [AppComponent],
      providers: [{provide: TaskService, useClass: MockTaskService}]
    }).compileComponents()
  }))

  beforeEach((done) => {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;

    taskService = fixture.debugElement.injector.get(TaskService);
    taskService.getAllTasks().subscribe(tasks => {
      task = tasks[0];
      comp.task = task;
      comp.ngOnInit();
      fixture.detectChanges();
      done()
    })
  })

  it('should not update value if form is invalid', () => {
    let spy = spyOn(taskService, 'updateTask')

    comp.form.controls['description'].setValue(null) // <------

    comp.onSubmit()

    expect(comp.form.valid).toBeFalsy();
    expect(spy).not.toHaveBeenCalled();
  })
})

注意箭头旁边的括号符号。我希望能够以某种方式使用它:

comp.form.controls.description.setValue(null)

我尝试过的

是尝试创建一些接口,例如:

interface ITask {
  description: string;
}

interface ITaskFormGroup extends FormGroup {
  value: ITask;
  controls: {
    description: AbstractControl;
  };
}

它的问题是控件是类型的AbstractControl,而所讨论的任务成员是类型字符串。因此,“控件”不能将ITASK作为通用接口实现。

另外,这是:

"compilerOptions": {
    "noPropertyAccessFromIndexSignature": false,
    // ...
}

...允许点通知,但是当我更改任务或iTask中的成员“描述”时,这仍然不会重构测试。

我觉得这个问题应该有一些解决方案,但是我可以帮助到达那里。我想知道将要花费什么努力,以便从长远来看是否值得。

完整的示例

尽管我认为您无法在 stackblitz ,我提供了一个更完整的示例。要解决运行测试的语法问题。

The Problem

My unit tests on a form break when I refactor an object's member because I can only access the formcontrols on a fromgroup by using bracket notation. This string based selection of the member under test will not get refactored with the code by any IDE.

Here's an example...

The object:

class Task {
  constructor(
    public id: number = 0,
    public description: string = '',    // <--- member under test
    public completed: boolean = false,
    public priority: Priority = Priority.Normal
  ) {}

  clone(): Task {
    return new Task(this.id, this.description, this.completed, this.priority);
  }
}

The component:

export class AppComponent implements OnInit {
  form!: FormGroup;
  task: Task;
  priorities = [Priority.Low, Priority.Normal, Priority.High];

  constructor(private fb: FormBuilder) {
    this.task = this.mockTask();
  }

  ngOnInit() {
    this.task = this.mockTask();
    this.form = this.fb.group({
      description: [
        this.task.description,
        Validators.compose([Validators.required, Validators.maxLength(50)]),
      ],
      priority: [this.task.priority],
    });
  }

  // ...
}

The test:

describe('AppComponent', () => {
  let comp: AppComponent
  let fixture: ComponentFixture<AppComponent>;
  let task: Task;
  let taskService: TaskService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      declarations: [AppComponent],
      providers: [{provide: TaskService, useClass: MockTaskService}]
    }).compileComponents()
  }))

  beforeEach((done) => {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;

    taskService = fixture.debugElement.injector.get(TaskService);
    taskService.getAllTasks().subscribe(tasks => {
      task = tasks[0];
      comp.task = task;
      comp.ngOnInit();
      fixture.detectChanges();
      done()
    })
  })

  it('should not update value if form is invalid', () => {
    let spy = spyOn(taskService, 'updateTask')

    comp.form.controls['description'].setValue(null) // <------

    comp.onSubmit()

    expect(comp.form.valid).toBeFalsy();
    expect(spy).not.toHaveBeenCalled();
  })
})

Note the bracket notation next to the arrow. I would like to be able to use this somehow:

comp.form.controls.description.setValue(null)

What I Have Tried

I have tried creating some interface, e.g:

interface ITask {
  description: string;
}

interface ITaskFormGroup extends FormGroup {
  value: ITask;
  controls: {
    description: AbstractControl;
  };
}

The problem with this is the controls are of type AbstractControl and the member of Task in question is of type string. So 'controls' can't implement ITask as a common interface.

Also, this:

"compilerOptions": {
    "noPropertyAccessFromIndexSignature": false,
    // ...
}

... allows for a dot-notation, but that would still not refactor the test when I change the member 'description' in Task or ITask.

I feel like this problem should have some solution, but I could use some help getting there. I would like to know what effort it would take so I can see if it is worth it in the long run.

Full Example

Although I don't think you can use a Test runner on Stackblitz, I provided a more complete example. To solve the syntax problem running the test should not be required.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

眼泪也成诗 2025-02-04 13:30:05

从FormGroup获得表单控件的正确方法是使用其 GET 函数:

const descControl = comp.form.get('description')
descControl.setValue(null)

这样,如果丢失了表单控件,至少您的测试将引起错误。

Angular团队已实施强大的打字表单。如果您有耐心等待Angular 14,这也可以帮助您改善测试。

The proper way of getting a form control from a FormGroup is to use its get function:

const descControl = comp.form.get('description')
descControl.setValue(null)

this way if the form control is missing, at least your tests will raise an error about it.

The angular team has implemented strong typed forms. That can also help you with improving testing if you have the patience to wait for angular 14.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文