TDD for ToDoMVC

Source code

In DOM TDD with JSDOM we saw using Mocha and Chai for frontend unit tests, with jsdom as a fake “browser”, to let jQuery work. Let’s write some tests for our ToDoMVC frontend.

Source Code

  1. Install dependencies. We need mocha, chai, and jsdom:

    $ npm install --save-dev mocha chai jsdom
    
  2. Small first test. Let’s make a file tests.js with one test:

    import $ from 'jquery';
    import {describe, it, beforeEach} from 'mocha';
    import {expect} from 'chai';
    import ToDos from './todo';
    
    describe('ToDo', () => {
        it('should import', () => {
            expect(ToDos).to.be.a('function');
        });
    });
    
  3. PyCharm run configuration. Make a Mocha run configuration, pointed at this tests file, with Extra Mocha options set to:

    --compilers js:babel-core/register
    
  4. Run it.

  5. Add test setup. Make a function inside describe to setup each test:

    beforeEach(() => {
        $('body').html(`
            <input id="newName"/>
            <ul id="todoList"></ul>
            `
        );
    
        // Avoid confusion, just reset these. Each test has to set.
        $.get = null;
        $.ajax = null;
    });
    
  6. Helper module. jQuery wants some globals before import. Let’s make a helper.js module which we import before any other imports:

    import jsdom from 'jsdom';
    global.document = jsdom.jsdom('<body></body>');
    global.window = document.defaultView;
    
  7. Import helper.js.

  8. Add tests. Add, one-by-one, each of the tests:

    ToDoMVC TDD tests.js
    import './helper';
    import $ from 'jquery';
    import {describe, it, beforeEach} from 'mocha';
    import {expect} from 'chai';
    import ToDos from './todo';
    
    describe('ToDo', () => {
        let sampleData = [
            {id: 1, name: 'One'},
            {id: 2, name: 'Two'}
        ];
        beforeEach(() => {
            $('body').html(`
                <input id="newName"/>
                <ul id="todoList"></ul>
                `
            );
    
            // Avoid confusion, just reset these. Each test has to set.
            $.get = null;
            $.ajax = null;
        });
    
        it('should import', () => {
            expect(ToDos).to.be.a('function');
        });
    
        it('should start with a ul and no li', () => {
            expect($('#todoList').length).eql(1);
            expect($('#todoList li').length).eql(0);
        });
    
        it('should do an initial render', () => {
            $.get = () => new $.Deferred().resolve({objects: sampleData});
            new ToDos();
            expect($('#todoList li').length).eql(sampleData.length);
        });
    
        it('should delete an item', () => {
            $.get = () => new $.Deferred().resolve({objects: sampleData});
            let todos = new ToDos();
            expect($('#todoList li').length).eql(sampleData.length);
    
            // Wire up $.ajax to simulate HTTP DELETE, then $.get to return
            // only one item
            $.ajax = () => new $.Deferred().resolve();
            $.get = () => new $.Deferred().resolve({
                objects: [sampleData[0]]
            });
            todos.delete(2);
            expect($('#todoList li').length).eql(sampleData.length - 1);
        });
    
    
    });