What is a better (more testable) way to structure this page-specific JS? I've created this (very) contrived example that demonstrates how we currently organize page specific JS in our app. Basically each page has a matching JS file that uses the module pattern to enclose page specific behaviors. Each module has an init() function that takes control IDs and wires up events, etc.

This has worked fairely well for us, however now we are wanting to add unit tests. Since many of our modules store state in private variables our unit tests end up stomping all over each other.

I think we need to stop using these singleton modules and instead use instances so that we can write better tests. What are some ways we could restructure this type of code so that it would be testable?

JS Fiddle here...

HTML

&lt;div&gt;
  &lt;input type="file" /&gt;&lt;br/&gt;
  &lt;input type="file" /&gt;&lt;br/&gt;
  &lt;input type="file" /&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;br/&gt;
  &lt;input id="btnUpload" type="button" value="Upload Files" /&gt;
  or &lt;a href="#" id="btnClear"&gt;Clear files&lt;/a&gt;
&lt;/p&gt;

&lt;script&gt;
    MyPage.init({
      clearLink: '#btnClear',
      uploadBtn: '#btnUpload',
      fileCtrls: "input[type='file']"
    });
&lt;/script&gt;


JS

MyPage = (function() {
    // private members
    var _files = [];
    var _initOptions;

    // public members
    return {
        init: function(options) {
            _initOptions = options;

            $(function() {
                // wire up the events
                $(options.clearLink).click(MyPage.clear);
                $(options.uploadBtn).click(MyPage.doUpload);
                $(options.fileCtrls).change(MyPage.addFile);
            });
        },

        addFile: function() {
            _files.push($(this).val());
        },

        doUpload: function() {
            alert('uploading ' + _files.length + ' files...');
        },

        clear: function() {
            _files = [];
            $(_initOptions.fileCtrls).val('');
        }
    }
})();
MyPage = (function() {
    var Page = {
        addFile: function(e) {
            this._files.push($(e.target).val());
        },

        doUpload: function() {
            alert('uploading ' + this._files.length + ' files...');
        },

        clear: function() {
            this._files = [];
            $(this.options.fileCtrls).val('');
        }
    }

    // public members
    return {
        init: function(options) {
            var o = Object.create(Page, {
                options: { value: options },
                _files: { value: [] }
            });

            $(function() {
                // wire up the events
                options.clearLink.click($.proxy(o.clear, o));
                options.uploadBtn.click($.proxy(o.doUpload, o));
                options.fileCtrls.change($.proxy(o.addFile, o));
            });
        },


    }
})();


Using Object.create

Live Example

ES5-shim for browser support.

As for testing use any old testing framework you like. Use jQuery.sub() to mock out jQuery