15

I want to proxy test results running on other environment in real time.

Here is some pseudo code that I want to make real:

  var test = proxy.getCurrentTest(); 
  // => {slow: 200, timeout: 2000, duration: 235, result: 'error'};

  var tmpIt = it('test1', function(){
      this.slow(test.slow);
      this.timeout(test.timeout);
  });
  tmpIt.close({
      duration: test.duration,
      result: test.result
  });
  // this should make this test red in the output, 
  // because the `result` is not 'success'

Is it anyhow possible to set test's result and duration without "really" running it? And get all the visual mocha output to the terminal?

edit: this question is not about how to pass variables with the test results form the child process to the main process. it's already working for me.

antpaw
  • 15,444
  • 11
  • 59
  • 88
  • If i understand your question correctly, you can set params via environment variables. http://stackoverflow.com/questions/16144455/mocha-tests-with-extra-options-or-parameters – Nazar Sakharenko Jan 25 '16 at 15:54
  • no its not about, providing extra options at all – antpaw Jan 25 '16 at 16:09
  • Looks like i get. You want to send comand to start tests from your main machine/site to other machine/site and print result? – Nazar Sakharenko Jan 25 '16 at 16:32
  • If i'm right, there is idea. You can run server on your test-runner machine, and listen for some commands, for example - run test with name from body/url. Then, you can spawn child process: child.exec("mocha", ["params"], ...., save child stdout to variable and send it back to main server. – Nazar Sakharenko Jan 25 '16 at 16:42
  • yes @NazarSakharenko that's the idea. With your solution am stuck at the same problem but now at the cli and not in js code. I can pass the all the variables form the child process to the main process. I don't know how to create a test at set its duration and outcome without running it, from those variables. – antpaw Jan 25 '16 at 19:39
  • Can you provide more expanded example that you want to get? – Alexey B. Jan 29 '16 at 08:58
  • @AlexeyB. i have added more comments to the code. what i want is actually very simple, I want to "fake" test results based on other results. – antpaw Jan 29 '16 at 09:11
  • 2
    According to your words "this should make this test red in the output, because the `result` is not 'success'" ```expect(test.result).to.be.equal('success')```? Sorry i really want to help, but got no idea that is your problem – Alexey B. Jan 29 '16 at 09:39
  • right, but it will ignore the duration, and i don't want to fake the duration with a setTimeout because that would mean the tests are twice as slow, first i have to wait for the actual test and then for the fake test. – antpaw Jan 29 '16 at 16:26

1 Answers1

3

Hope I understood the requirements correctly. What I implemented is a test result forwarder to mocha that integrated into mocha.

To integrate with mocha this implementation describes a custom mocha interface to proxy test results from tests executed in another environment.

To use this interface the -u argument has to be passed to mocha when running mocha

> mocha -u ./path/to/proxy-interface ...

Note that ./path/to/proxy-interface is the path that mocha uses in a require call to require the interface module.

The proxy interface is responsible to expose a proxyTest function to the global context such as mocha's BDD interface does with it, invoke the passed function to get the test results and forward the test results while preserving the number of tests executed displayed by the test runner.

var Mocha = require('mocha');

var Suite = Mocha.Suite;
var Test = Mocha.Test;
var escapeRe = require('escape-string-regexp');

module.exports = function(suite) {
  var suites = [suite];

  suite.on('pre-require', function(context, file, mocha) {
    // A bit hacky since we require mocha internal common interface module
    var common = require('mocha/lib/interfaces/common')(suites, context);

    context.run = mocha.options.delay && common.runWithSuite(suite);

    context.proxyTest = function(title, fn) {
      var suite = suites[0];
      if (suite.pending) {
        fn = null;
      }
      var test = new ProxyTest(title, fn);
      test.file = file;
      suite.addTest(test);
      return test;
    };
  });
};

var Runnable = Mocha.Runnable;
var inherits = require('util').inherits;

function ProxyTest(title, fn) {
  Runnable.call(this, title, null);
  this.pending = !fn;
  this.type = 'test';
  this.body = (fn || '').toString();

  this.fn = fn;
}

inherits(ProxyTest, Runnable);

ProxyTest.prototype.run = function(done) {
  var proxiedTestResult = this.fn();

  this.duration = proxiedTestResult.duration;
  this.timedOut = this.timeout() > proxiedTestResult.timeout;

  done(proxiedTestResult.result);
};

ProxyTest.prototype.clone = function() {
  var test = new ProxyTest(this.title, this.fn);
  test.timeout(this.timeout());
  test.slow(this.slow());
  test.enableTimeouts(this.enableTimeouts());
  test.retries(this.retries());
  test.currentRetry(this.currentRetry());
  test.globals(this.globals());
  test.parent = this.parent;
  test.file = this.file;
  test.ctx = this.ctx;
  return test;
};

The code above overrides Mocha's Runnable run implementation and runs the passed function to get test results and sets the required fields in the ProxyTest interface to be compatible with mocha tests.

Usage

In your tests use the proxyTest global to register a new test with mocha

var proxy = {
  getErrorTestResult() {
    return {slow: 200, timeout: 2000, duration: 50, result: 'error'};
  },

  getTimeoutTestResult() {
    return {slow: 200, timeout: 2000, duration: 3000 };
  },

  getSlowTestResult() {
    return {slow: 200, timeout: 2000, duration: 235 };
  },

  getSuccessTestResult() {
    return {slow: 200, timeout: 2000, duration: 50 };
  }
}

proxyTest('error', proxy.getErrorTestResult);
proxyTest('timeout', proxy.getTimeoutTestResult);
proxyTest('slow', proxy.getSlowTestResult);
proxyTest('success', proxy.getSuccessTestResult);

Output

Mocha Output

Implications

The drawback of this approach is that a custom interface has to be passed to mocha AND that you cannot use mocha BDD vocabulary like describe. The second drawback can be eliminated if you "extend" (In this case: copy some code) the BDD interface of mocha:

var Mocha = require('mocha');
/**
 * Module dependencies.
 */

var Suite = Mocha.Suite;
var Test = Mocha.Test;
var escapeRe = require('escape-string-regexp');

/**
 * BDD-style interface - extended with proxy functionality:
 *
 *      describe('Array', function() {
 *        describe('#indexOf()', function() {
 *          it('should return -1 when not present', function() {
 *            // ...
 *          });
 *
 *          it('should return the index when present', function() {
 *            // ...
 *          });
 *        });
 *      });
 *
 * @param {Suite} suite Root suite.
 */
module.exports = function(suite) {
  var suites = [suite];

  suite.on('pre-require', function(context, file, mocha) {
    // A bit hacky since we require mocha internal common interface module
    var common = require('mocha/lib/interfaces/common')(suites, context);

    context.before = common.before;
    context.after = common.after;
    context.beforeEach = common.beforeEach;
    context.afterEach = common.afterEach;
    context.run = mocha.options.delay && common.runWithSuite(suite);
    /**
     * Describe a "suite" with the given `title`
     * and callback `fn` containing nested suites
     * and/or tests.
     */

    context.describe = context.context = function(title, fn) {
      var suite = Suite.create(suites[0], title);
      suite.file = file;
      suites.unshift(suite);
      fn.call(suite);
      suites.shift();
      return suite;
    };

    /**
     * Pending describe.
     */

    context.xdescribe = context.xcontext = context.describe.skip = function(title, fn) {
      var suite = Suite.create(suites[0], title);
      suite.pending = true;
      suites.unshift(suite);
      fn.call(suite);
      suites.shift();
    };

    /**
     * Exclusive suite.
     */

    context.describe.only = function(title, fn) {
      var suite = context.describe(title, fn);
      mocha.grep(suite.fullTitle());
      return suite;
    };

    /**
     * Describe a specification or test-case
     * with the given `title` and callback `fn`
     * acting as a thunk.
     */

    var it = context.it = context.specify = function(title, fn) {
      var suite = suites[0];
      if (suite.pending) {
        fn = null;
      }
      var test = new Test(title, fn);
      test.file = file;
      suite.addTest(test);
      return test;
    };

    /**
     * Exclusive test-case.
     */

    context.it.only = function(title, fn) {
      var test = it(title, fn);
      var reString = '^' + escapeRe(test.fullTitle()) + '$';
      mocha.grep(new RegExp(reString));
      return test;
    };

    /**
     * Pending test case.
     */

    context.xit = context.xspecify = context.it.skip = function(title) {
      context.it(title);
    };

    /**
     * Number of attempts to retry.
     */
    context.it.retries = function(n) {
      context.retries(n);
    };

    context.proxyTest = function(title, fn) {
      var suite = suites[0];
      if (suite.pending) {
        fn = null;
      }
      var test = new ProxyTest(title, fn);
      test.file = file;
      suite.addTest(test);
      return test;
    };
  });
};

var Runnable = Mocha.Runnable;
var inherits = require('util').inherits;

function ProxyTest(title, fn) {
  Runnable.call(this, title, null);
  this.pending = !fn;
  this.type = 'test';
  this.body = (fn || '').toString();

  this.fn = fn;
}

inherits(ProxyTest, Runnable);

ProxyTest.prototype.run = function(done) {
  var proxiedTestResult = this.fn();

  this.duration = proxiedTestResult.duration;
  this.timedOut = this.timeout() > proxiedTestResult.timeout;

  done(proxiedTestResult.result);
};

ProxyTest.prototype.clone = function() {
  var test = new ProxyTest(this.title, this.fn);
  test.timeout(this.timeout());
  test.slow(this.slow());
  test.enableTimeouts(this.enableTimeouts());
  test.retries(this.retries());
  test.currentRetry(this.currentRetry());
  test.globals(this.globals());
  test.parent = this.parent;
  test.file = this.file;
  test.ctx = this.ctx;
  return test;
};
saintedlama
  • 6,838
  • 1
  • 28
  • 46