About test intention

The first step of the TDD cycle is to write a failing test. When this new test is green without changing the production code, it is considered that the code is already doing too much. However sometimes the intention of a new test is not to drive the development of something new but to document an alternative usage.

Let me share an example.

I like to use an EDA approach for the front-end too, instead of having to pass parameters in a component tree. I like to implement this with an event bus known and used by the components who need or provide data.

The creation of an event bus is a good opportunity for a coding kata to build up my muscle memory about incremental value delivery.

The first test is mostly about product discovery.

describe('event bus', ()=> {

    let eventBus;
    beforeEach(()=> {
        eventBus = new EventBus();
    });

    it('will update registered component', ()=> {
        let received;
        let component = {
            update: (value)=> { received = value; }
        };
        eventBus.register(component, 'this event');
        eventBus.notify('this event', 42);

        expect(received).to.equal(42);
    });
});

Then I like to triangulate in a separate test.

    it('will not update registered component for a different event', ()=> {
        let received;
        let component = {
            update: (value)=> { received = value; }
        };
        eventBus.register(component, 'this event');
        eventBus.notify('that event', 42);

        expect(received).to.equal(undefined);
    });

Then we cover collections.

    it('will update all components registered with this event', ()=> {
        let first;
        let second;
        eventBus.register({ update: (value)=> { first = value; }}, 'event');
        eventBus.register({ update: (value)=> { second = value; }}, 'event');
        eventBus.notify('event', 42);

        expect(first).to.equal(42);
        expect(second).to.equal(42);
    });

With this first version, the bus will always call the update(value) function of a registered component. When a component needs to listen to several events, this first version will lead to conditionals in the callback, which becomes messy pretty fast. We wouldn’t want that, so we add a new test calling for a more advanced feature.

    it('will call registered callback', ()=> {
        let received;
        let callback = (value)=> { received = value; }
        eventBus.register(callback, 'that-event');
        eventBus.notify('that-event', 42);

        expect(received).to.equal(42);
    });

This is one of the points of the coding kata exercice that I really like: practicing our hability to identify increments and implement incremental value delivery. One scenario being that the next increment offers a new feature without removing previous features.

Now, if you tend to forget, like me, how to use bind to allow the callback to use the attributes of the component, just add a test describing how it works.

    it('needs binding to call registered callback in context', ()=> {
        let received;
        class Foo {
            constructor() {
                this.value = 39;
                eventBus.register(this.listen.bind(this), 'event');
            }
            listen(value) {
                received = this.value + value;
            }
        }
        new Foo();
        eventBus.notify('event', 3);

        expect(received).to.equal(42);
    });

When writting a new test, one thing that I found very useful is to identify the intention of the test. For this last one, the intention is to remind me something I tend to forget. The test is immediately green for me and I get the red by removing the trailing bind which is exactly what the test wants to document.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.