Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocha: create and finalize test programmatically

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.

like image 426
antpaw Avatar asked Jan 19 '16 09:01

antpaw


1 Answers

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;
};
like image 130
saintedlama Avatar answered Oct 20 '22 22:10

saintedlama