8

Note: This is not a real App but a distilled version of the problem.

I have a simple application fake Todo that uses @computed to get the value of 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));
    }
}

Testing TodoItemComputed.isSelected I could do something like this:

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);
    });
});

For some reasons, I have to refactor my app and I use reactions instead of computed (for example because I have to display 10,000 items and the selection changed rapidly and calling TodoListComputed.setSelected caused all computation to re-run on all items).

Therefore I change it to use reaction (it seems that when I use autorun I can use the same tests as for the computed version):

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));
    }
}

Question: How to test the autorun version?

Bonus Question: How to test the code that it does not matter if I use autorun or computed?

Michael_Scharf
  • 29,028
  • 15
  • 60
  • 87

0 Answers0