使用 RxJS Marbles 测试 Angular Reactive Forms

Ved*_*ran 8 jasmine rxjs angular rxjs-marbles

角度组件

public setupObservables() {
  this.formFieldChanged$ = this.formField
    .valueChanges
    .pipe(
        debounceTime(100),
        distinctUntilChanged((a, b) => a === b),
    )
}
Run Code Online (Sandbox Code Playgroud)

茉莉花测试

import { of } from 'rxjs';
import { marbles } from 'rxjs-marbles/jasmine';  
...

it('should update value on debounced formField change', marbles(m => {
  const values = { a: "1", b: "2", c: "3" };

  const fakeInputs = m.cold('a 200ms b 50ms c', values);
  const expected = m.cold('100ms a 250ms c', values);

  // works on stackblitz but otherwise gives TS2540 compiler error
  // cannot assign to a read-only property
  component.formField.valueChanges = fakeInputs; 
  component.setupObservables();

  m.expect(component.formFieldChanged$).toBeObservable(expected);
}));
Run Code Online (Sandbox Code Playgroud)

stackblitz.com 示例

目的是使用大理石测试来测试Observable具有 Angular 反应形式的上下文中的代码。

  • 这种方法有意义吗?
  • 如何最好的嘲弄valueChanges一个的FormField对象?
  • 有没有更好的方法来构建这类测试?

sat*_*ime 6

问题是 - 你想测试什么。这是单元测试还是 e2e 测试?如果它是一个单元测试 - 模拟反应形式,仅涵盖您的逻辑,那么您就没有问题valueChanges,因为它被模拟并且您可以控制它。

如果这是一个 e2e 测试 - 你不应该重新分配valueChanges。不应模拟/替换任何内容,因为这是一个端到端测试。

不过,如果您想更改valueChanges- 使用https://github.com/krzkaczor/ts-essentials#writable

(Writable<typeof component.formField>component.formField).valueChanges = fakeInputs; 
Run Code Online (Sandbox Code Playgroud)

它将使得属性类型可写。

如果是单元测试,就我个人而言,我会投票支持模拟反应式形式,因为在单元测试中我们只需要测试我们的单元,它的依赖项应该被模拟/存根。

注入我们想要模拟的部分

作为一个选项,您可以将表单作为组件的依赖项移动到组件声明中的提供程序。

@Component({
    selector: 'app-component',
    templateUrl: './app-component.html',
    styleUrls: ['./app-component.scss'],
    providers: [
        {
            provide: 'form',
            useFactory: () => new FormControl(),
        },
    ],
})
export class AppComponent {
    public formFieldChanged$: Observable<unknown>;

    constructor(@Inject('form') public readonly formField: FormControl) {
    }

    public setupObservables(): void {
        this.formFieldChanged$ = this.formField
            .valueChanges
            .pipe(
                debounceTime(100),
                distinctUntilChanged((a, b) => a === b),
            );
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以简单地在测试中注入模拟而不是它。

it('should update value on debounced formField change', marbles(m => {
    const values = { a: "1", b: "2", c: "3" };

    const fakeInputs = m.cold('a 200ms b 50ms c', values);
    const expected = m.cold('100ms a 250ms c', values);

    const formInput = {
        valueChanges: fakeInputs,
    };

    const component = new AppComponent(formInput as any as FormControl);

    component.setupObservables();
    m.expect(component.formFieldChanged$).toBeObservable(expected);
}));
Run Code Online (Sandbox Code Playgroud)