Question

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
      });
    });
  }());
Was it helpful?

Solution 2

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);
        });
      });

    });

  }());

OTHER TIPS

I solved the problem by encoding the binary file as a hex string, stuffing it into a blob, and calling the function that takes the File object. I'm using Jasmine to test a file load function. FileLoader is the class that I wrote that has the function loadFromFile.

  beforeEach(function() {
    return this.fileLoader = new FileLoader()  });

  it("can load this bin file safely", function() {
    var blob, fileContentsEncodedInHex;

    fileContentsEncodedInHex = ["\x45\x6e\x63\x6f\x64\x65\x49\x6e\x48\x65\x78\x42\x65\x63\x61\x75\x73\x65\x42\x69\x6e\x61\x72\x79\x46\x69\x6c\x65\x73\x43\x6f\x6e\x74\x61\x69\x6e\x55\x6e\x70\x72\x69\x6e\x74\x61\x62\x6c\x65\x43\x68\x61\x72\x61\x63\x74\x65\x72\x73"];

    blob = new Blob(fileContentsEncodedInHex);

    this.fileLoader.loadFromFile(blob);
  });

The function in the File loader class looks something like this (in coffeescript). You should definitely add error handling to your commercial code...

  #
  # Load the file
  #
  # @param [File] file
  #   The DOM file object
  #
  loadFromFile: (file) ->
    # create the file reader
    # assign the file load handler
    # read the array as a buffer, which will trigger the handler
    reader = new FileReader()
    reader.onloadend = this._handleOnLoadEnd
    reader.readAsArrayBuffer(file)
    return

I wrote the following small python app to output file contents as a hex encoded string that can be used as the contents of a javascript string. (FYI, this is my first python script in about 12 years, I'm sure it's not efficient, but it was quick) Many thanks to a co-worker for the output string. I'm not sure why the code block is getting butchered in the display. My apologies.

import sys
fulldoc = ""
with open('your file', 'rb') as readFile:  
    while 1:  character = readFile.read(1)
        if not character:
          break
        fulldoc = fulldoc + "\\x" + character.encode("hex")
print fulldoc

Additional tidbits... I don't know your exact situation but I would suggest the following...

If you need to add a new binary at any time and re-run the tests, then you should make a test for each kind of binary file that you are adding at random. Test both positive and negative files (ie, files that should work with your app, files that should not work with your app). That is what the unit test frameworks are there for. In the future, if you find that a file that breaks your app due to a bug in your code, you can fix the bug, add a new unit test to load that binary and the tests should then pass. If you happen to break the file processing code on accident at some point, the unit tests are always there to show you something is wrong.

I think you can get around running an extra grunt server on a different port to serve up the binary file. In the latest version of karma, you can define some details around how the files are included and served by the karma server. So you would include your test data in the files, and tell Karma to serve, but not watch or include those files

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',
  /* NEW LINE NEEDED BELOW! */
  {pattern: 'src/specs/data/**', watched: false, included: false, served: true},
  'generated/js/complete-app.js',
  'src/specs/**/*.spec.js'
];

Then in your test, you need to get the proper location of the file into the xhr.open method. If you pass in an empty string into xhr.open('GET',''), and dump the responseText, you'll get an output with all of the included scripts/files being passed to Karma, and there, you'll find paths that start with '/base/' and sometimes '/absolute/'. Not sure 100% what Karma is doing, but I tried out using the '/base/' as the prefix for the xhr.open path and it seems to work:)

 var url = '/base/src/specs/data/MyInterface.class';
 xhr.open('GET', url, true); 

I am also working on a project that involves reading binary data files in the client, and also working with Karma for testing, so it was great to read your question for inspiration. I ended up finding the new functionality in Karma really useful in simplifying the approach!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top