Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocha does not reset 'this' on every test run #1062

Closed
FooBarWidget opened this issue Dec 6, 2013 · 5 comments
Closed

Mocha does not reset 'this' on every test run #1062

FooBarWidget opened this issue Dec 6, 2013 · 5 comments

Comments

@FooBarWidget
Copy link

Pretty much all unit test frameworks I've used, allow me to run each test in an isolated environment. I was surprised to find out that this is not the case with mocha:

describe('something', function(){
  it('should one', function(){
    this.ok = true;
  })

  it('should two', function(){
    console.log(this.ok);
  })
})

The second test prints "true". I expected "undefined".

Is this how mocha is supposed to work, or did I do something wrong? I cannot find any documentation on what the intended behavior is. If this is the intended behavior, how do I reset 'this'? Do I have to manually delete everything?

@travisjeffery
Copy link
Contributor

ya this is expected. doesn't make much sense to put something on this when it's going to be used like a var scoped to its containing function anyway.

@FooBarWidget
Copy link
Author

Wait, I don't get your reasoning. In other unit testing frameworks it's quite common to set an instance variable in the setup/beforeEach function, which is then used in the test function. Something like this:

class TestSuite {
   User user;

   function setup() {
       this.user = createUser();
   }

   function test_one() {
       do_something_with(this.user);
   }
}

Upon running every test function, a new TestSuite instance is created, so that all instance variables are empty at the beginning. Are you telling me not to do something like this in mocha? Are you telling me to keep all my state in local variables within the test function? Then how do I share data between the beforeEach and the test function?

@travisjeffery
Copy link
Contributor

ya that works in mocha. you're saying two different things now.

this is how you do in mocha what you have there

describe('something', function(){
  before(function(){
    this.name = 'travis';
  });

  it('should have a name', function(){
    this.name.should.equal('travis');
  })
})

@FooBarWidget
Copy link
Author

Well, consider the following use case:

var net = require('net');

describe("foo", function() {
    this.timeout(2000);

    beforeEach(function() {
        this.createSockets = function(callback) {
            var self = this;

            function maybeDone() {
                if (self.sock1 && self.sock2) {
                    callback();
                }
            }

            var sock1 = net.connect(80, 'www.chello.nl', function(err) {
                self.sock1 = sock1;
                maybeDone();
            });
            var sock2 = net.connect(80, 'www.chello.nl', function() {
                self.sock2 = sock2;
                maybeDone();
            });
        }
    });

    it("works 1", function(done) {
        var self = this;
        this.createSockets(function() {
            // Two sockets connected!
            done();
        });
    });

    it("works 2", function(done) {
        var self = this;
        this.createSockets(function() {
            // Two sockets connected!
            done();
        });
    });

    it("works 3", function(done) {
        var self = this;
        this.createSockets(function() {
            // Two sockets connected!
            done();
        });
    });
});

This test suite creates two socket connections on every test, and calls the callback only after both socket connections have been created. Pretty simple stuff to test networking code. But when I run it I get a surprising result:

$ ./node_modules/.bin/mocha -R spec foo_spec.js


  foo
    ✓ works 1 (759ms)
    ✓ works 2 (918ms)
    1) works 2
    ✓ works 3 (920ms)
    2) works 3


  3 passing (3s)
  2 failing

  1) foo works 2:
     Error: done() called multiple times
      at multiple (/Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:175:31)
      at done (/Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:181:26)
      at /Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:197:9
      at /Users/hongli/Sites/socketio.test/foo_spec.js:39:13
      at maybeDone (/Users/hongli/Sites/socketio.test/foo_spec.js:12:21)
      at Socket.<anonymous> (/Users/hongli/Sites/socketio.test/foo_spec.js:18:17)
      at Socket.g (events.js:175:14)
      at Socket.EventEmitter.emit (events.js:92:17)
      at Object.afterConnect [as oncomplete] (net.js:883:10)

  2) foo works 3:
     Error: done() called multiple times
      at multiple (/Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:175:31)
      at done (/Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:181:26)
      at /Users/hongli/Sites/socketio.test/node_modules/mocha/lib/runnable.js:197:9
      at /Users/hongli/Sites/socketio.test/foo_spec.js:47:13
      at maybeDone (/Users/hongli/Sites/socketio.test/foo_spec.js:12:21)
      at Socket.<anonymous> (/Users/hongli/Sites/socketio.test/foo_spec.js:22:17)
      at Socket.g (events.js:175:14)
      at Socket.EventEmitter.emit (events.js:92:17)
      at Object.afterConnect [as oncomplete] (net.js:883:10)

"done() called multiple times"? After some investigation, it turns out that the following check doesn't work properly:

if (self.sock1 && self.sock2) {

The intention is to only continue if both sockets have been connected. However, because the "works 1" test's this.sock1 and this.sock2 are preserved, upon running "works 2" this check succeeds immediately, without waiting for the second socket to connect. This causes done() to be called. Then later, when the second socket is connected, the callback and thus done() are called again, resulting in this error.

The same thing happens in "works 3".

I totally did not expect this to happen. The first thing I did was running "works 2" individually... which doesn't trigger the problem because there are no leftover data from a previous test! It took me quite a while to debug this.

None of the other unit test frameworks that I've worked with so far preserve instance variables. Why does mocha? What do you suggest me to do in this case? Right now I'm working around this by putting all my state in a this.state object, which I reset on every test.

At the very least, you should document this behavior.

@travisjeffery
Copy link
Contributor

you can clean up your socks in the beforeEach or in an afterEach

or those socks don't even need to be set on this. you could do this

var net = require('net');


describe("foo", function() {
  this.timeout(2000);

  beforeEach(function() {
    this.createSockets = function(callback) {
      var self = this, _sock1, _sock2;

      function maybeDone() {
        if (_sock1 && _sock2) {
          callback();
        }
      }

      var sock1 = net.connect(80, 'www.chello.nl', function(err) {
        _sock1 = sock1;
        maybeDone();
      });
      var sock2 = net.connect(80, 'www.chello.nl', function() {
        _sock2 = sock2;
        maybeDone();
      });
    }
  });

  it("works 1", function(done) {
    var self = this;
    this.createSockets(function() {
            // Two sockets connected!
            done();
          });
  });

  it("works 2", function(done) {
    var self = this;
    this.createSockets(function() {
            // Two sockets connected!
            done();
          });
  });

  it("works 3", function(done) {
    var self = this;
    this.createSockets(function() {
            // Two sockets connected!
            done();
          });
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants