Get help with testing, discuss unit testing strategies etc.


Post by AaronTovo »

I'm looking for a good way to test my ExtJS 4 MVC controller classes.

For example I would like to test that my controller responds to a given message by loading a store from a static mock data file.

I'm having trouble creating controllers because they create stores and the Ext code needs an Ext.application to create the store. I can create a full version of my application in a test file but that is much more than I want to test. If I try to create a mock app I run into problems where the 'get' functions generated by the controller's refs block are not defined. (that's the problem I've run into in my latest attempt, but I've tried other variations of test code that all run into similar problems.)

What do I need to generate these refs functions? And, more generally, are there any examples of controller tests? I haven't been able to find any.

My test looks like this:
StartTest(function(t) {
    var aStore, srStore, form, mStore, drStore, fCtlr;
    var searchButton,
        patientIdBox,
        patientNameBox,
        patientBirthDateBox,
        accessionNumberBox,
        studyDescriptionBox,
        modalityBox,
        dateRangeBox,
        clearResultsBox;

    t.diag("Search filter form test");

    t.chain(
        function(next) {
            // we need an app to create a controller.
            var app;
            Ext.application({
                name: 'StudySearch',
                appFolder: '/vitrea-home/study-search',
                models: ['Archive', 'DateRange', 'Modality'],
                stores: ['Archives', 'DateRanges', 'Modalities'],
                controllers: ['Filter'],
                launch: function() {
                    app = this;
                    var app = this;
                    fCtlr = app.getController('Filter');
                    aStore = app.getStore('Archives');
                    srStore = app.getStore('SearchResults');
                    mStore = app.getStore('Modalities');
                    drStore = app.getStore('DateRanges');

                    t.ok(fCtlr, 'now we can test the Filter controller with the mock app');
                    next();
                }
            });
        },
        function (next) {
            // override with mock data urls
            aStore.model.proxy.url = './siesta-test/mock/archive.json';
            srStore.proxy.url = srStore.mintURL = '/vitrea-home/siesta-test/mock/studies.json';
            t.loadStoresAndThen(aStore, srStore, next);
        },
...
        function(next) {
            t.done();
        }
    );
})
And here is a typical controller definition in my code:
Ext.define('StudySearch.controller.Filter', {
    extend: 'Ext.app.Controller',
    stores: ['Archives', 'DateRanges', 'Modalities'],
    views: ['ArchivesList', 'SearchForm', 'SelectField'],

    refs: [{
        ref: 'filter',
        selector: 'filter'
    },{
        ref: 'archivesList',             // ******* this fails to generate a getArchivesList() function *******
        selector: 'archiveslist'
    },{
        ref: 'searchForm',              // ******* this fails to generate a getSearchForm() function *******
        selector: 'searchform'
    }],

    init: function() {
        this.control({
            'archiveslist': {
                select: this.onArchiveSelect
            },
            'viewport > #search-west > searchform button': {
                click: this.onSearch
            },
            'viewport > #search-west > searchform textfield': {
                keyup: this.onKeyUp
            }
        });

        // listen for events from other controllers
        if (this.application) {
            this.application.on({
                logout: this.onLogout,
                scope: this
            });
        }
    },

... function defs ...
}

Post by mats »

We're still pondering the best way to test controllers, without having to involve big application objects. The way they're written makes them hard to test without loads of mocking it seems, investigation ongoing...

Post by AaronTovo »

One correction: the 'get' functions are generated for the items in the refs array but the functions are empty, as in "function Empty() {}". Perhaps the selectors are not finding the xtypes they are given in the refs array.

Post by AaronTovo »

I've worked out a solution to my immediate problem. It's not great, because it requires extra work and couples the test code with the product code, but I can live with it. If I create the components myself then the refs work (I couldn't find where the 'get' functions are stored, but my my code runs so the functions are in there somewhere!)
            Ext.application({
                name: 'StudySearch',
                appFolder: '/vitrea-home/study-search',
                models: ['Archive', 'DateRange', 'Modality'],
                stores: ['Archives', 'DateRanges', 'Modalities'],
                controllers: ['Filter'],
                launch: function() {
                    app = this;
                    var app = this;
                    fCtlr = app.getController('Filter');
                    aStore = app.getStore('Archives');
                    srStore = app.getStore('SearchResults');
                    mStore = app.getStore('Modalities');
                    drStore = app.getStore('DateRanges');

                    [b]var searchFormArray = Ext.ComponentQuery.query('searchform');
                    t.notOk(searchFormArray[0], 'SearchForm view not found');
                    var alViewArray = Ext.ComponentQuery.query('archiveslist');
                    t.notOk(alViewArray[0], 'SearchForm view not found');

                    form = Ext.create('StudySearch.view.SearchForm', { renderTo: Ext.getBody() });  // I want to show this one and using the autoRender flag w/show() is too slow
                    aList = Ext.create('StudySearch.view.ArchivesList', {  });

                    t.ok(fCtlr.getSearchForm(), 'search form access function works');
                    t.ok(fCtlr.getArchivesList(), 'archives list access function works');[/b]

                    t.ok(fCtlr, 'now we can test the Filter controller with the mock app');
                    next();
                }
            });
I think making controller testing easier would be a major plus for Siesta. That's were the most important business logic lives for many apps. Also, I think testing controllers is a big problem with jasmine because of the way the initialization of apps and controllers are tightly coupled in ExtJS. So solving this problem would help a lot of people.

Post by mats »

Can you post a simple case where you only use the controller? Just want a real failing test case.

Post by AaronTovo »

I'm not sure I understand what you are asking for. There is an attempt to test a controller without a mock app in https://forum.bryntum.com/viewtopic.php?f=20&t=2041.

The Ext.create('StudySearch.controller.Filter', {}) fails because there is no application object.

If I could create the controller I would test member functions such as the following:
    validateSearchData: function(theForm) {
        if (theForm && theForm.isValid()) {
            var params = theForm.getValues();

            params.accessionNumber = Ext.String.trim(params.accessionNumber);
            params.patientId = Ext.String.trim(params.patientId);
            params.patientName = Ext.String.trim(params.patientName);
            params.studyDescription = Ext.String.trim(params.studyDescription);

            // if "ALL" is selected then we don't filter by modality at all
            if (! params.modality || params.modality === this.getModalitiesStore().ANY_MODALITY) {
                params.modality = '';
            }

            if (params.studyDate === this.getDateRangesStore().ANY_DATE_RANGE) {
                params.studyDate = '';
            }

            this.application.fireEvent('newsearchparams', this.getSearchParamString(), params.clearResults);
            this.application.fireEvent('mintsearchvalidated', params);
        }
    },

Post by mats »

Perfect, just want to have some additional test scenario once we start looking into this. Were you in the online meetup last night btw? :)

Post by AaronTovo »

Yes, it was very good. I passed on the recording to a couple of co-workers.

Some feedback from my point of view:
1) It probably wasn't necessary to make the case for unit testing in such a forum - I think anyone who is there already has experienced the need for automated testing
2) There were a lot of little thing that were new to me - such as several APIs and "autoCheckGlobals: true".
3) I liked the jslint test. I think it would be worth adding to your examples. I hand-typed the code from the recording.
4) The demo did a good job of showing off the kinds of ui actions siesta can do.

Post by mats »

Thanks for good feedback. I thought it might be nice to start 'light' with some theory/background. Not everyone realizes why testing is a good idea, there are just so many reasons worth mentioning...

Post Reply