Recently I’ve run into several Javascript tests that test implementation details instead of public behavior. These type of tests are almost as broken as Chauncey’s clavicle, and are most common in dynamic languages like Javascript, where there are no public/protected/private access modifiers to protect developers from themselves. Coders may be tempted to test implementation details because it is often quicker to write an implementation test than testing the actual behavior. The pain comes when your team decides to refactor some implementation details for one reason or another, and suddenly you have 30 failing tests, even though the public facing behavior of the code hasn’t changed.

Good tests ensure the public interface of a component works as documented and allow the component’s internal implementation details to be refactored without breaking existing tests.

Javascript testing don’ts:

  • Don’t test private methods:
    assertEqual(val, component._pseudoPrivateFunc());
  • Don’t test private state:
    assertEqual(val, component._pseudoPrivateAttribute);
  • Don’t test private dependencies:
    var obj = new FooComponent();
    var spy = sinon.spy(obj.dependency._pseudoPrivateFunc);
    obj.doSomething();
    assertTrue(spy.called);
  • Don’t test multiple operations in the same test:
    obj.doSomething();
    this.assertEqual(expected, obj.attr);
    
    obj.doSomethingElse();
    this.assertEqual(different, obj.attr);

Javascript testing do’s:

  • Test the behavior of an object’s public interface.
  • Mock out all dependencies:
    var dep = sinon.mock({mockedFunc: function() {return true;}});
    var objToTest.setDep(dep);
  • If a test requires a private method to be mocked, it may be an indication that your object is doing too much work. Consider moving the logic into a separate object dependency that can be easily mocked.
  • Limit the number of assertions per test. If a test contains many asserts, it either needs to be broken up into multiple tests, or the asserts should be grouped together into a custom assert function.
    this.assertThatMyComponentIsHappy(obj);
  • [personal preference] Tests should be named as concisely as possible, and any extra explanation should be included as a comment on the test or the method you’re testing. reallyLongTestNamesAreDifficultToReadEspeciallyIfYoureTryingToFindOrRerunATestThatJustFailed.
  • If refactoring a piece of code causes a bad test to break, take the time to properly refactor the test code instead of only doing the minimum amount of work to get it passing again.