diff --git a/Gruntfile.js b/Gruntfile.js
index 04bc6ab3ef35604f46c38f2db627b3afdf7e9331..7355989ff908a79ab54d209ac5e68da509bb1b79 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,6 +1,9 @@
-'use strict';
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
 
 module.exports = function (grunt) {
+    'use strict';
 
     require('./spec_runner')(grunt);
 
@@ -145,38 +148,33 @@ module.exports = function (grunt) {
                 template: require('grunt-template-jasmine-requirejs'),
                 ignoreEmpty: true
             },
-            'backend-unit-testsuite': {
-                options: specConfigFor('unit', 'adminhtml', 8000)
-            },
-            'backend-integration-testsuite': {
-                options: specConfigFor('integration', 'adminhtml', 8000)
-            },
-            'frontend-unit-testsuite': {
-                options: specConfigFor('unit', 'frontend', 3000)
-            },
-            'frontend-integration-testsuite': {
-                options: specConfigFor('integration', 'frontend', 3000)
-            }
+            'backend-unit-testsuite':           specConfigFor('unit', 'adminhtml', 8000),
+            'backend-integration-testsuite':    specConfigFor('integration', 'adminhtml', 8000),
+            'frontend-unit-testsuite':          specConfigFor('unit', 'frontend', 3000),
+            'frontend-integration-testsuite':   specConfigFor('integration', 'frontend', 3000)
         }
     });
 
     function specConfigFor(type, dir, port) {
         return {
-            host: 'http://localhost:' + port,
-            specs: specPathFor(type, dir, 'Spec'),
-            helpers: specPathFor(type, dir, 'Helper'),
-            templateOptions: {
-                requireConfigFile: [
-                    'lib/web/app-config.js',
-                    '<%= config.path.spec %>/' + type + '/config.js'
-                ]
+            src: '<%= config.path.spec %>/env.js',
+            options: {
+                host: 'http://localhost:' + port,
+                specs: specPathFor(type, dir, 'Spec'),
+                helpers: specPathFor(type, dir, 'Helper'),
+                templateOptions: {
+                    requireConfigFile: [
+                        'lib/web/app-config.js',
+                        '<%= config.path.spec %>/' + type + '/config.js/'
+                    ]
+                }
             }
         }
-    };
+    }
 
     function specPathFor(type, dir, suffix) {
-        return '<%= config.path.spec %>/' + type + '/**/' + dir + '/**/*' + suffix + '.js'
-    };
+        return '<%= config.path.spec %>/' + type + '/**/' + dir + '/**/*' + suffix + '.js';
+    }
 
     // Clean var & pub folders
     grunt.registerTask('cleanup', [
diff --git a/dev/tests/js/spec/unit/Magento/Ui/adminhtml/eventsSpec.js b/dev/tests/js/spec/unit/Magento/Ui/adminhtml/eventsSpec.js
index ec222391eb5a8388af0ff5fdc7aada0ea255a0b6..3dbf1805da45d357c3e92471ec2ceda15163ae84 100644
--- a/dev/tests/js/spec/unit/Magento/Ui/adminhtml/eventsSpec.js
+++ b/dev/tests/js/spec/unit/Magento/Ui/adminhtml/eventsSpec.js
@@ -1,14 +1,23 @@
 /**
  * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
  */
+/* global jasmine: true, expect: true, it: true, describe: true */
 define([
     'Magento_Ui/js/lib/events'
 ], function (EventBus) {
     'use strict';
 
     describe('EventBus', function () {
-        it('has <on> method', function () {
-            expect('on' in EventBus).toBe(true);
+        describe('<on> method', function () {
+            it('calls passed callback when event is triggered', function () {
+                var callback = jasmine.createSpy('callback');
+
+                EventBus.on('someEvent', callback);
+
+                EventBus.trigger('someEvent');
+
+                expect(callback).toHaveBeenCalled();
+            });
         });
     });
-});
\ No newline at end of file
+});
diff --git a/spec_runner.js b/spec_runner.js
index 6a686067dc5a70b1ccf1d94a84830b2046e731d1..faa920a556bb327e516f23cb80a2bf65b3a8b0fa 100644
--- a/spec_runner.js
+++ b/spec_runner.js
@@ -1,62 +1,138 @@
-'use strict';
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+/* global __dirname: true */
+
+module.exports = function (grunt) {
+    'use strict';
 
-module.exports = function(grunt) {
     var connect     = require('connect'),
         logger      = require('morgan'),
         serveStatic = require('serve-static'),
         fs          = require('fs');
 
+    /**
+     * Defines if passed file path exists
+     *
+     * @param  {String} path
+     * @return {Boolean}
+     */
+    function exists(path) {
+        return fs.existsSync(path);
+    }
+
+    /**
+     * Restricts url's which lead to '/_SpecRunner.html', '/dev/tests' or '.grunt' folders from being modified
+     *
+     * @param  {String} url
+     * @return {Boolean}
+     */
     function canModify(url) {
         return url.match(/^\/(\.grunt)|(dev\/tests)|(_SpecRunner\.html)/) === null;
-    };
+    }
 
-    grunt.registerMultiTask('specRunner', function(grunt) {
+    grunt.registerMultiTask('specRunner', function () {
         var app = connect(),
             options,
             area,
+            theme,
             share,
-            moduleRoot,
-            libPath;
+            middlewares;
 
         options = this.options({
             port: 3000,
+            theme: 'blank',
             areaDir: 'adminhtml',
             shareDir: 'base',
-            enableLogs: false
+            enableLogs: false,
+            middleware: null
         });
 
         area        = options.areaDir;
         share       = options.shareDir;
-        moduleRoot  = '/app/code/Magento/';
-        libPath     = '/lib/web';
+        theme       = options.theme;
 
         if (options.enableLogs) {
             app.use(logger('dev'));
         }
 
-        function assembleUrl(module, area, path) {
-            return moduleRoot + module + '/view/' + area + '/web/' + path;
-        }
-
-        app.use(function(req, res, next) {
-            var url = req.url,
-                match = url.match(/^\/Magento_([^\/]+)(\/.+)$/),
+        app.use(function (req, res, next) {
+            var url     = req.url,
+                match   = url.match(/^\/([A-Z][^\/]+)_(\w+)\/(.+)$/),
+                app,
                 module,
                 path,
-                exist;
+                getModuleUrl,
+                getThemeUrl;
+
+            /**
+             * Returns path to theme root folder
+             *
+             * @return {String}
+             */
+            function themeRoot() {
+                return [
+                    '/app/design',
+                    area,
+                    app,
+                    theme
+                ].join('/');
+            }
+
+            /**
+             * Based on 'thematic' parameter, returnes either path to theme's lib,
+             *     or 'lib/web'.
+             *
+             * @param  {Boolean} thematic
+             * @return {String}
+             */
+            function lib(thematic) {
+                return thematic ? themeRoot() + '/web' : '/lib/web';
+            }
 
             if (match !== null) {
-                module  = match[1];
-                path    = match[2];
-                url     = assembleUrl(module, area, path),
-                exist   = fs.existsSync(url);
+                app     = match[1];
+                module  = match[2];
+                path    = match[3];
+
+                /**
+                 * Assembles modular path. If 'shared' flag provided and is truthy,
+                 *     will use share dir instead of area one.
+                 *
+                 * @param  {Boolean} shared
+                 * @return {String}
+                 */
+                getModuleUrl = function (shared) {
+                    return [
+                        '/app/code',
+                        app,
+                        module,
+                        'view',
+                        shared ? share : area,
+                        'web',
+                        path
+                    ].join('/');
+                };
 
-                if (!exist) {
-                    url = assembleUrl(module, share, path);
-                }
+                /**
+                 * Assembles theme modular path.
+                 *
+                 * @return {String}
+                 */
+                getThemeUrl = function () {
+                    return [
+                        themeRoot(),
+                        app + '_' + module,
+                        'web',
+                        path
+                    ].join('/');
+                };
+
+                url = exists(url = getThemeUrl()) ? url : getModuleUrl(true);
 
             } else if (canModify(url)) {
-                url = libPath + url;
+                url = (exists(url = lib(true)) ? url : lib()) + req.url;
             }
 
             req.url = url;
@@ -64,8 +140,18 @@ module.exports = function(grunt) {
             next();
         });
 
+        if (options.middleware && typeof options.middleware === 'function') {
+            middlewares = options.middleware(connect, options);
+
+            if (Array.isArray(middlewares)) {
+                middlewares.forEach(function (middleware) {
+                    app.use(middleware);
+                });
+            }
+        }
+
         app.use(serveStatic(__dirname));
 
         app.listen(options.port);
     });
-}
\ No newline at end of file
+};