Search code examples
javascriptjasminearraybufferkarma-runner

How do I load a binary file during javascript unit test?


In my application, the user uses HTML5 drag and drop to process a binary file. That part of the code works fine. In chrome, I drag a binary file and use a FileReader to create an arrayBuffer. That all seems to work fine. I am writing tests for this functionality and I am at a loss. How do I load a binary file into my unit test? For the bit of code I am testing, I only need an arrayBuffer. Currently, I am creating the arrayBuffer manually, but that isn't a sustainable solution. In order for my tests to be effective, I need to be able to throw a new binary file in at anytime and make a new test. My testing environment is testacular+jasmine.

( function() {"use strict";
    function loadSimpleDataView() {
      //instead of defining ArrayBuffer, 
      //I want it to be generated based upon an external file
      var buffer = new ArrayBuffer(4), dataView = new DataView(buffer), int8View = new Int8Array(buffer);
      int8View.set([0x00,0x01,0x02,0x03]);

      return dataView;
    }

    describe('mymodule', function() {

      it('mytest', function() {
        var dataView  = loadSimpleDataView();

        expect(dataView).toBeDefined();
        //do rest of tests
      });
    });
  }());

Solution

  • I use grunt to build my project and run my unit tests. Below is my grunt.js, testacular.conf.js, and test (javaclassstreamreader.spec.js). In short, grunt starts up grunt-server which is configured to serve the binary data files. Testacular is configured to proxy the grunt-server. In the test, XMLHttpRequest is used to retrieve the binary file. Everything works, but it seems a bit complex. Is there an easier way?

    grunt.js:

    /*global module:false*/
    module.exports = function(grunt) {"use strict";
    
      // Project configuration.
      grunt.initConfig({
        pkg : '<json:package.json>',
        meta : {
          banner : '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
        },
        lint : {
          files : ['grunt.js', 'src/*.js', 'src/public/js/**/*.js', 'src/specs/**/*.js']
        },
        watch : {
          files : '<config:lint.files>',
          tasks : 'default'
        },
        exec : {
          ensure_generated_directory : {
            command : 'mkdir -p generated/js/'
          }
        },
        clean : {
          all : ['generated']
        },
        jshint : {
          files : '<config:lint.files>',
          options : {
            curly : true,
            eqeqeq : true,
            forin : true,
            immed : true,
            latedef : true,
            newcap : true,
            noarg : true,
            sub : true,
            undef : true,
            unused : true,
            strict : true,
            boss : true,
            eqnull : true,
            es5 : true,
            browser : true,
            jquery : true,
            devel : true
          },
          globals : {
            //jasmine
            describe : false,
            it : false,
            expect : false,
            //commonjs
            require : false,
            exports : true,
            //angular
            angular : false
          }
        },
        'closure-compiler' : {
          frontend : {
            closurePath : 'closure-compiler',
            js : ['src/*.js', 'src/public/js/**/*.js'],
            jsOutputFile : 'generated/js/complete-app.js',
            options : {
              externs : 'externs.js',
              compilation_level : 'SIMPLE_OPTIMIZATIONS',
              language_in : 'ECMASCRIPT5_STRICT',
              logging_level : 'ALL',
              debug : null,
              warning_level : 'verbose',
              summary_detail_level : 3,
              formatting : ['PRETTY_PRINT', 'PRINT_INPUT_DELIMITER'],
              common_js_entry_module : 'src/public/js/app.js',
              process_common_js_modules : null,
              process_jquery_primitives : null,
              common_js_module_path_prefix : 'src'
            }
          }
        },
        testacularServer : {
          integration : {
            options : {
              keepalive : true
            },
            configFile : 'testacular.conf.js',
            autoWatch : false,
            singleRun : true
          }
        },
        server : {
          port : 18081,
          base : './src/specs/data'
        }
      });
    
      // Default task.
      grunt.registerTask('default', 'lint exec:ensure_generated_directory closure-compiler server testacularServer:integration');
      grunt.loadNpmTasks('grunt-contrib-watch');
      grunt.loadNpmTasks('grunt-closure-compiler');
      grunt.loadNpmTasks('grunt-exec');
      grunt.loadNpmTasks('grunt-contrib-clean');
      grunt.loadNpmTasks('grunt-testacular');
    };
    

    testacular.conf.js:

    // Testacular configuration
    // Generated on Tue Jan 01 2013 03:17:01 GMT-0500 (EST)
    /*global basePath:true */
    /*global files:true */
    /*global JASMINE:false */
    /*global JASMINE_ADAPTER:false */
    /*global exclude:true */
    /*global reporters:true */
    /*global port:true */
    /*global runnerPort:true */
    /*global colors:true */
    /*global logLevel:true */
    /*global LOG_INFO:false */
    /*global autoWatch:true */
    /*global browsers:true */
    /*global captureTimeout:true */
    /*global singleRun:true */
    
    // base path, that will be used to resolve files and exclude
    basePath = '.';
    
    
    // list of files / patterns to load in the browser
    files = [
      JASMINE,
      JASMINE_ADAPTER,
      'src/public/lib/jquery/1.7.2/jquery-1.7.2.min.js',
      'src/public/lib/jquery-ui/1.8.20.custom/jquery-ui-1.8.20.custom.min.js',
      'src/public/lib/angular/1.0.1/angular-1.0.1.min.js',
      'src/public/lib/filer.min.js',
      'generated/js/complete-app.js',
      'src/specs/**/*.spec.js'
    ];
    
    
    // list of files to exclude
    exclude = [
    ];
    
    
    // test results reporter to use
    // possible values: 'dots', 'progress', 'junit'
    reporters = ['progress'];
    
    
    // web server port
    port = 18080;
    
    
    // cli runner port
    runnerPort = 9100;
    
    //proxy
    proxies = {
        '/test-data/': 'http://localhost:18081/'
    };
    
    // enable / disable colors in the output (reporters and logs)
    colors = true;
    
    
    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel = LOG_INFO;
    
    
    // enable / disable watching file and executing tests whenever any file changes
    autoWatch = true;
    
    
    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers = ['Chrome'];
    
    
    // If browser does not capture in given timeout [ms], kill it
    captureTimeout = 5000;
    
    
    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun = false;
    

    javaclassstreamreader.spec.js:

    /*global module$javaclassstreamreader:false */
    /*global waitsFor:false */
    /*global runs:false */
    /*global dump:false */
    
    
    ( function() {"use strict";
    
        var JavaClassStreamReader = module$javaclassstreamreader.JavaClassStreamReader;
    
        function loadSimpleDataView(callback) {
          var dataView;
          var xhr = new XMLHttpRequest();
          xhr.open('GET', '/test-data/MyInterface.class', true); 
          xhr.responseType = 'arraybuffer';
          xhr.onload = function() {
              dataView = new DataView(this.response);
              callback(dataView);
          };
          xhr.onerror = dump;
          xhr.send();
        }
    
        describe('javaclassstreamreader', function() {
    
          it('reader can be constructed', function() {
            var hasData = false,reader;
    
            loadSimpleDataView(function(dataView) {
              reader = new JavaClassStreamReader(dataView);
              hasData = true;
            });
    
            waitsFor(function() {
              return hasData;
            }, "Never retrieved file", 3000);
            runs(function() {
    
              expect(reader.offset).toBe(0);
    
              var firstBytes = reader.getU4();
              dump(firstBytes.toString(16));
              expect(firstBytes).toBe(0xcafebabe);
              expect(reader.maxOffset).toBe(126);
            });
          });
    
        });
    
      }());