如何测试mobx反应?

时间:2018-12-08 01:54:42

标签: typescript unit-testing mobx

注意:这不是真正的应用,而是问题的简化版本。

我有一个简单的伪造的Todo应用程序,它使用@computed来获取TodoItemComputed.isSelected的值:

import { computed, observable, action } from 'mobx';

export class TodoItemComputed {
    readonly id: string;
    readonly list: TodoListComputed;
    constructor(id: string, list: TodoListComputed) {
        this.id = id;
        this.list = list;
    }
    // ** This is the attribute we are interested in and we want to test!
    @computed
    get isSelected() {
        return this.id === this.list.selectedId;
    }
}

export class TodoListComputed {
    @observable.shallow
    private serverData: string[] = [];

    @observable
    selectedId = '';

    @action
    setServerData(data: string[]) {
        this.serverData = data;
    }
    @action
    setSelected(id: string) {
        this.selectedId = id;
    }
    @computed
    get todoItems() {
        return this.serverData.map(d => new TodoItemComputed(d, this));
    }
}

测试TodoItemComputed.isSelected我可以做这样的事情:

import { TodoListComputed } from '../TodoExample';

// Using jesthere, but the test framework should not matter...
describe('isSelected', function() {
    it('should have no Item selected initially', function() {
        const todoList = new TodoListComputed();
        todoList.setServerData(['foo', 'bar']);
        expect(todoList.todoItems[0].isSelected).toBe(false);
        expect(todoList.todoItems[1].isSelected).toBe(false);
    });
    it('should select the correct item', function() {
        const todoList = new TodoListComputed();
        todoList.setServerData(['foo', 'bar']);
        todoList.setSelected('bar');
        expect(todoList.todoItems[0].isSelected).toBe(false);
        expect(todoList.todoItems[1].isSelected).toBe(true);
    });
});

由于某些原因,我必须重构我的应用,并且我使用反应而不是计算(例如,因为我必须显示10,000个项目,并且选择迅速改变,并且调用TodoListComputed.setSelected导致重新运行所有计算)所有项目)。

因此,我将其更改为使用reaction(看来,当我使用autorun时,可以使用与computed版本相同的测试):

import { autorun, computed, observable, action } from 'mobx';

export class TodoItemReaction {
    readonly id: string;
    @observable
    isSelected = false;

    constructor(id: string) {
        this.id = id;
    }
    @action
    setSelected(isSelected: boolean) {
        this.isSelected = isSelected;
    }
}

export class TodoListReaction {
    @observable.shallow
    private serverData: string[] = [];
    @observable
    selectedId = '';

    constructor() {
        reaction(
            () => [this.setSelected, this.todoItems],
            () => {
                this.todoItems.forEach(t => {
                    t.setSelected(t.id === this.selectedId);
                });
            }
        );
    }

    @action
    setServerData(data: string[]) {
        this.serverData = data;
    }
    @action
    setSelected(id: string) {
        this.selectedId = id;
    }
    @computed
    get todoItems() {
        return this.serverData.map(d => new TodoItemReaction(d));
    }
}

问题:如何测试autorun版本?

奖励问题:如何测试是否使用autoruncomputed的代码?

1 个答案:

答案 0 :(得分:0)

无论您如何实施,您都应该能够测试这两种实施方式。顺便说一句,我认为这应该适用于任何测试,避免“了解”实现,测试需求。

当您使用 @computed 在反应中设置项目的 todoItems 时,您的实现问题在于 isSelected 列表中 setSelected 的使用。这将导致重新计算 @computed todoItems。 这是因为您在初始化类时将 isSelected 设置为 false

换句话说,@computed todoItems 正在“聆听”每个项目的 @observable isSelected 的变化。

这是一个非常简单且快速的实现(没有 reaction):

export class TodoItem {
  readonly id: string;
  @observable isSelected = false;

  constructor(id: string) {
    this.id = id;
  }
  
  @action
  setSelected(isSelected: boolean) {
    this.isSelected = isSelected;
  }
}

export class TodoList {
  @observable.shallow
  todoItems: TodoItem[] = [];

  @action
  setServerData(data: string[]) {
    this.todoItems = data.map((d) => new TodoItem(d));
  }

  @action
  setSelected(id: string) {
    this.todoItems.forEach((t) => t.setSelected(t.id === id));
  }
}