0

I am working on an socket.io IRC and I don't want users to have a long username. I wrote the following (mocha) test to verify that the server doesn't send out a response to every connected socket when a longer username is provided:

  it("should not accept usernames longer than 15 chars", function (done) {
    var username = "a".repeat(server.getMaxUsernameLength() + 1);
    client1.emit("username change", username);
    client2.on("chat message", function (data) {
      throw Error("Fail, server did send a response.");
    });
    setTimeout(function () {
      done();
    }, 50);
  });

This currently does work, but it's far from optimal. What if my CI platform is slower or the server does respond after more than 50 ms? What's the best way to fail a test when a response is given, or should I structure my tests differently?

Thanks!

P.s. This question is different from Testing asynchronous function with mocha, because while the problem does have to do with asynchronous testing, I am aware of the done() method (and I'm using it obviously).

Community
  • 1
  • 1
  • so you want to fail the test when exactly? – Iceman Jul 25 '16 at 17:35
  • 1
    Possible duplicate of [Testing asynchronous function with mocha](http://stackoverflow.com/questions/12159846/testing-asynchronous-function-with-mocha) – rpadovani Jul 25 '16 at 17:36
  • I want the test to fail when the server confirms the username and sends it to all the connected clients ("User X changed his name to user Z"). – Pieter Tolsma Jul 25 '16 at 18:18

1 Answers1

0

What you're trying to do is verify that the callback to client2.on("chat message"... is never called. Testing for negative cases can be tough, and your case seems to be exacerbated by the fact that you're trying to do a complete end-to-end (client-to-server-to-client) integration test. Personally, I would try to test this in a unit case suite and avoid introducing the complexity of asynchronicity to the test.

However, if it must be done, here's a tip from Eradicating Non-Determinism in Tests:

This is the trickiest case since you can test for your expected response, but there's nothing to do to detect a failure other than timing-out. If the provider is something you're building you can handle this by ensuring the provider implements some way of indicating that it's done - essentially some form of callback. Even if only the testing code uses it, it's worth it - although often you'll find this kind of functionality is valuable for other purposes too.

Your server should send some sort of notice to client1 that it's going to ignore the name change, even if you aren't testing, but since you are you could use such a notification to verify that it really didn't send a notification to the other client. So something like:

it("should not accept usernames longer than 15 chars", function (done) {
  var chatSpy = sinon.spy();
  client2.on("chat message", chatSpy);
  client1.on('error', function(err) {
    assertEquals(err.msg, 'Username too long');
    assert(chatSpy.neverCalledWith(...));
    done();
  });

  var username = "a".repeat(server.getMaxUsernameLength() + 1);
  client1.emit("username change", username);
});

would be suitable.

Also, if for whatever reason, server.getMaxUsernameLength() ever starts returning something other than 15, the best case scenario is that your test description becomes wrong. It can become worse if getMaxUsernameLength and the server code for handling the name change event don't get their values from the same place. A test probably should not rely on the system under test to provide test values.

Will
  • 2,163
  • 1
  • 22
  • 22