Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test code of web worker

Taking the example code from https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/basic_usage , the following is run by a web worker

// in worker.js
onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

run by code in an Angular service/factory

var myWorker = new Worker("worker.js");

I would like to be able to unit test the code in worker.js, ideally running it as part of an Angular service/factory (in a separate app running in the web worker?), so I can use the DI system to inject mocks of dependencies, and have the unit test code looking much like the tests for any other service. How can I do this?

like image 286
Michal Charemza Avatar asked Sep 30 '22 02:09

Michal Charemza


1 Answers

There is a way to do this, where the main code run in a web worker is run as part of a separate, manually-bootstrapped, Angular module.

In order to load Angular in a web worker

  • The environment needs to be tinkered with/monkey patched/hacked so Angular doesn't throw various exceptions when it loads and bootstraps due to missing window and document objects
  • Angular must then be included via importScripts
  • Then the module you would like to run must be included via importScripts
  • Then the module manually bootstrapped.

Example code that does this:

// worker.js

// Angular needs a global window object
var window = self;

// Skeleton properties to get Angular to load and bootstrap.
self.history = {};
var document = {
  readyState: 'complete',
  querySelector: function() {},
  createElement: function() {
    return {
      pathname: '',
      setAttribute: function() {}
    }
  }
};

// Load Angular: must be on same domain as this script
importScripts('angular.js');

// Put angular on global scope
angular = window.angular;

// Standard angular module definition
importScripts('worker-app.js');

// No root element seems to work fine
angular.bootstrap(null, ['worker-app']);

The code of the Angular module itself can then be very standard: with services/factories defined as desired. In this case, none are needed and I've opted to put the simple code of the example as a run callback (I've omitted the console.logs from the question).

// worker-app.js
(function() {
  'use strict';

  var app = angular.module('worker-app', []);

  app.run(function($window) {
    $window.onmessage = function(e) {
      var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
      $window.postMessage(workerResult);
    };
  });

})();

This can then be tested using exactly the same tests as if it weren't meant to run in a web worker:

// worker-app-spec.js
describe('worker-app', function() {
  'use strict';

  var $window;

  beforeEach(module('worker-app'));

  beforeEach(inject(function(_$window_) {
    $window = _$window_;
  }));

  beforeEach(function() {
    spyOn($window, 'postMessage');
  })

  it('attaches to $window onmessage', function() {
    var data = [2,3];
    var result = 'Result: ' + (data[0] * data[1]);
    $window.onmessage({data: data});
    expect($window.postMessage).toHaveBeenCalledWith(result);
  });
});

I'm not such a fan of hacking the environment to get Angular to load, as it feels very brittle to me, so other answers very welcome! The above code was tested using AngularJS v1.3.7.

like image 113
Michal Charemza Avatar answered Oct 26 '22 07:10

Michal Charemza