Repository: mochajs/mocha

Problem statement:
Configure 'slow' property per test
Hi! I'm using mocha in the browser.

Just the same way I can configure a single test 'timeout' threshold:

``` javascript
it('should take less than 500ms', function(done){
  this.timeout(500);
  setTimeout(done, 300);
});
```

it would be great to configure 'slow' threshold as there may be some functions heavier than others. Maybe:

``` javascript
it('would take less than 500ms', function(done){
  this.slow(500);
  setTimeout(done, 800);
})
```

Cheers.



Hints:
+1 from me

Would it make sense to segregate fast and slow tests into separate files/directories and run them separately?

@nanodeath if you wanted to yeah sure, no reason you couldn't. Or you could even keep them in context and "tag" them, something like `it('should do something @slow', callback)` and `--grep @slow` etc

This issue has been inactive for over 1 month so I'm closing it. If you think it's still an issue re-open. - tjbot

I think this new feature is not yet implemented, is it?

+1, maybe I'll give this a try?

+1

We often have to mock libraries that are required by requirejs and it adds a considerable amount of overhead to the tests. While we'll ideally find a way to optimize this, it's not a high priority and we honestly don't care that much if the tests run for 50-100 ms each. Being able to suppress the warnings for these specific tests would be nice.


Patch:
diff --git a/lib/context.js b/lib/context.js
--- a/lib/context.js
+++ b/lib/context.js
@@ -40,6 +40,19 @@ Context.prototype.timeout = function(ms){
   return this;
 };
 
+/**
+ * Set test slowness threshold `ms`.
+ *
+ * @param {Number} ms
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.slow = function(ms){
+  this.runnable().slow(ms);
+  return this;
+};
+
 /**
  * Inspect the context void of `._runnable`.
  *
diff --git a/lib/mocha.js b/lib/mocha.js
--- a/lib/mocha.js
+++ b/lib/mocha.js
@@ -52,6 +52,7 @@ function image(name) {
  *   - `reporter` reporter instance, defaults to `mocha.reporters.Dot`
  *   - `globals` array of accepted globals
  *   - `timeout` timeout in milliseconds
+ *   - `slow` milliseconds to wait before considering a test slow
  *   - `ignoreLeaks` ignore global leaks
  *   - `grep` string or regexp to filter tests with
  *
@@ -68,6 +69,7 @@ function Mocha(options) {
   this.ui(options.ui);
   this.reporter(options.reporter);
   if (options.timeout) this.timeout(options.timeout);
+  if (options.slow) this.slow(options.slow);
 }
 
 /**
@@ -239,6 +241,19 @@ Mocha.prototype.timeout = function(timeout){
   return this;
 };
 
+/**
+ * Set slowness threshold in milliseconds.
+ *
+ * @param {Number} slow
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.slow = function(slow){
+  this.suite.slow(slow);
+  return this;
+};
+
 /**
  * Run tests and invoke `fn()` when complete.
  *
diff --git a/lib/reporters/base.js b/lib/reporters/base.js
--- a/lib/reporters/base.js
+++ b/lib/reporters/base.js
@@ -119,13 +119,6 @@ exports.cursor = {
   }
 };
 
-/**
- * A test is considered slow if it
- * exceeds the following value in milliseconds.
- */
-
-exports.slow = 75;
-
 /**
  * Outut the given `failures` as a list.
  *
@@ -227,8 +220,8 @@ function Base(runner) {
   runner.on('pass', function(test){
     stats.passes = stats.passes || 0;
 
-    var medium = exports.slow / 2;
-    test.speed = test.duration > exports.slow
+    var medium = test.slow() / 2;
+    test.speed = test.duration > test.slow()
       ? 'slow'
       : test.duration > medium
         ? 'medium'
diff --git a/lib/runnable.js b/lib/runnable.js
--- a/lib/runnable.js
+++ b/lib/runnable.js
@@ -36,6 +36,7 @@ function Runnable(title, fn) {
   this.async = fn && fn.length;
   this.sync = ! this.async;
   this._timeout = 2000;
+  this._slow = 75;
   this.timedOut = false;
 }
 
@@ -61,6 +62,21 @@ Runnable.prototype.timeout = function(ms){
   return this;
 };
 
+/**
+ * Set & get slow `ms`.
+ *
+ * @param {Number} ms
+ * @return {Runnable|Number} ms or self
+ * @api private
+ */
+
+Runnable.prototype.slow = function(ms){
+  if (0 === arguments.length) return this._slow;
+  debug('timeout %d', ms);
+  this._slow = ms;
+  return this;
+};
+
 /**
  * Return the full title generated by recursively
  * concatenating the parent's full title.
diff --git a/lib/runner.js b/lib/runner.js
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -176,7 +176,6 @@ Runner.prototype.failHook = function(hook, err){
 Runner.prototype.hook = function(name, fn){
   var suite = this.suite
     , hooks = suite['_' + name]
-    , ms = suite._timeout
     , self = this
     , timer;
 
@@ -485,4 +484,4 @@ function filterLeaks(ok) {
     });
     return matched.length == 0 && (!global.navigator || 'onerror' !== key);
   });
-}
\ No newline at end of file
+}
diff --git a/lib/suite.js b/lib/suite.js
--- a/lib/suite.js
+++ b/lib/suite.js
@@ -57,6 +57,7 @@ function Suite(title, ctx) {
   this._afterAll = [];
   this.root = !title;
   this._timeout = 2000;
+  this._slow = 75;
   this._bail = false;
 }
 
@@ -78,6 +79,7 @@ Suite.prototype.clone = function(){
   debug('clone');
   suite.ctx = this.ctx;
   suite.timeout(this.timeout());
+  suite.slow(this.slow());
   suite.bail(this.bail());
   return suite;
 };
@@ -98,6 +100,22 @@ Suite.prototype.timeout = function(ms){
   return this;
 };
 
+/**
+ * Set slow `ms` or short-hand such as "2s".
+ *
+ * @param {Number|String} ms
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.slow = function(ms){
+  if (0 === arguments.length) return this._slow;
+  if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000;
+  debug('slow %d', ms);
+  this._slow = parseInt(ms, 10);
+  return this;
+};
+
 /**
  * Sets whether to bail after first error.
  *
@@ -126,6 +144,7 @@ Suite.prototype.beforeAll = function(fn){
   var hook = new Hook('"before all" hook', fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._beforeAll.push(hook);
   this.emit('beforeAll', hook);
@@ -145,6 +164,7 @@ Suite.prototype.afterAll = function(fn){
   var hook = new Hook('"after all" hook', fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._afterAll.push(hook);
   this.emit('afterAll', hook);
@@ -164,6 +184,7 @@ Suite.prototype.beforeEach = function(fn){
   var hook = new Hook('"before each" hook', fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._beforeEach.push(hook);
   this.emit('beforeEach', hook);
@@ -183,6 +204,7 @@ Suite.prototype.afterEach = function(fn){
   var hook = new Hook('"after each" hook', fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._afterEach.push(hook);
   this.emit('afterEach', hook);
@@ -200,6 +222,7 @@ Suite.prototype.afterEach = function(fn){
 Suite.prototype.addSuite = function(suite){
   suite.parent = this;
   suite.timeout(this.timeout());
+  suite.slow(this.slow());
   suite.bail(this.bail());
   this.suites.push(suite);
   this.emit('suite', suite);
@@ -217,6 +240,7 @@ Suite.prototype.addSuite = function(suite){
 Suite.prototype.addTest = function(test){
   test.parent = this;
   test.timeout(this.timeout());
+  test.slow(this.slow());
   test.ctx = this.ctx;
   this.tests.push(test);
   this.emit('test', test);


Test patch:
diff --git a/test/runnable.js b/test/runnable.js
--- a/test/runnable.js
+++ b/test/runnable.js
@@ -41,6 +41,14 @@ describe('Runnable(title, fn)', function(){
     })
   })
 
+  describe('#slow(ms)', function(){
+    it('should set the slow threshold', function(){
+      var run = new Runnable;
+      run.slow(100)
+      run.slow().should.equal(100);
+    })
+  })
+
   describe('.title', function(){
     it('should be present', function(){
       new Runnable('foo').title.should.equal('foo');
@@ -206,4 +214,4 @@ describe('Runnable(title, fn)', function(){
     })
 
   })
-})
\ No newline at end of file
+})
diff --git a/test/suite.js b/test/suite.js
--- a/test/suite.js
+++ b/test/suite.js
@@ -9,6 +9,7 @@ describe('Suite', function(){
     beforeEach(function(){
       this.suite = new Suite('To be cloned');
       this.suite._timeout = 3043;
+      this.suite._slow = 101;
       this.suite._bail = true;
       this.suite.suites.push(1);
       this.suite.tests.push('hello');
@@ -26,6 +27,10 @@ describe('Suite', function(){
       this.suite.clone().timeout().should.equal(3043);
     });
 
+    it('should copy the slow value', function(){
+      this.suite.clone().slow().should.equal(101);
+    });
+
     it('should copy the bail value', function(){
       this.suite.clone().bail().should.be.true;
     });
@@ -74,6 +79,25 @@ describe('Suite', function(){
     });
   });
 
+  describe('.slow()', function(){
+    beforeEach(function(){
+      this.suite = new Suite('A Suite');
+    });
+
+    describe('when no argument is passed', function(){
+      it('should return the slow value', function(){
+        this.suite.slow().should.equal(75);
+      });
+    });
+
+    describe('when argument is passed', function(){
+      it('should return the Suite object', function(){
+        var newSuite = this.suite.slow(5000);
+        newSuite.slow().should.equal(5000);
+      });
+    });
+  });
+
   describe('.bail()', function(){
     beforeEach(function(){
       this.suite = new Suite('A Suite');
@@ -170,6 +194,7 @@ describe('Suite', function(){
     beforeEach(function(){
       this.first = new Suite('First suite');
       this.first.timeout(4002);
+      this.first.slow(200);
       this.second = new Suite('Second suite');
       this.first.addSuite(this.second);
     });
@@ -182,6 +207,10 @@ describe('Suite', function(){
       this.second.timeout().should.equal(4002);
     });
 
+    it('copies the slow value', function(){
+      this.second.slow().should.equal(200);
+    });
+
     it('adds the suite to the suites collection', function(){
       this.first.suites.should.have.length(1);
       this.first.suites[0].should.equal(this.second);