Better Javascript testing through ScrewUnit

Writing Good Tests

it provides documentation, explaining the intended functioning of the system as well as how the source code works;

it supports ongoing development, as you bit-by-bit write a failing test and make it pass;

it supports later refactorings and prevents regression as you add other features;

and it requires little modification as the implementation of the system changes, especially changes to unrelated code.

This section focuses principally on tests as documentation. To provide documentation, as well as support future modification, a test should be readable and well organized. Here are some recommendations on how to do just that.

Use Nested Describes to Express Context

Often, when you test a system (a function, an object), it behaves differently in different contexts. Use nested describes liberally to express the context under which you make an assertion.

describe("Caller#prioritize", function() {
describe("when there are two callers in the queue", function() {
describe("and one caller has been waiting longer than another", function() {
...
});
});
});

In addition to using nested describes to express context, use them to organize tests by the structural properties of your source code and programming language. In Javascript this is typically prototype and function. A parent describe for a prototype contains nested describes for each of its methods. If you have cross-cutting concerns (e.g., related behavior that spans across methods or prototypes), use a describe to group them conceptually.

In this example, one parent describe is used for all Car behavior. There is a describe for each method. Finally, cross-cutting concerns like callbacks and logging are grouped because of their conceptual affinity.

Test Size

Individual tests should be short and sweet. It is sometimes recommended to make only one assertion per test:

it("chooses the caller who has been waiting the longest", function() {
expect(Caller.prioritize()).to(equal, caller_waiting_the_longest);
});

According to some, the ideal test is one line of code. In practice, it may be excessive to divide your tests to be this small. At ten lines of code (or more), a test is difficult to read quickly. Be pragmatic, bearing in mind the aims of testing.

Although one assertion per test is a good rule of thumb, feel free to violate the rule if equal clarity and better terseness is achievable:

Preconditions

It is ideal, if there is any chance that your preconditions are non-obvious, to make precondition asserts in your test. The last example, were it more complicated, might be better written:

it("decrements the luck field by the given amount", function() {
expect(man.luck()).to(equal, 5);
man.decrement_luck(3);
expect(man.luck()).to(equal, 2)
});

Whitespace, as seen here, can be helpful in distinguishing setup and preconditions from the system under test (SUT) and its assertions. It is nice to be consistent in your use of whitespace (e.g., “always follow a group of preconditions by a newline”). But it is better to use whitespace as makes the most sense given the context. As with everything in life, do it consciously and deliberately, but change your mind frequently.

Behavioral Testing

Behavioral testing, that is, asserting that certain functions are called rather than certain values returned, is best done with closures. The dynamic nature of JavaScript makes mocking frameworks mostly unnecessary.

Have you tested ScrewUnit on MSIE? I really enjoy it, but my specs break in a strange way in IE (no output if a test has failed / suceeded, just a blank page with roman numbers) and this is a bit of a deal-breaker, as one of the things I want to test is consistency across browsers… It could be also the fault of my particular specs, I didn’t have the time to investigate it futher…

May 26, 2008 at 11:44 am

Nick Kallen says:

Hi Stiff,

Screw.Unit only supports Firefox and Safari at the moment, sorry. IE support will happen in the next release, in a week or so.

When are we going to realize that *we* as developers have the power of choice. It is us who controls market share.

*SORRY* This application doesn’t support that browser, it has been determined for the best possible experience in factors of reliability, security, and service to our shared experience that sub-standard technology that is invested in market share and profit – not usability – needs to be dropped. * I realize this sounds bitter and mal adjusted – it is still true.*

It takes informing, position and facts to educate a community. Building a piece of software that educates and holds the users hand will take engineers that can educate, speak, guide, learn and elucidate users in a way that removes mystery and concentrates on practicality.

that said, i do check ie7, firefox and safari.
the people at opera are in denial and need to concentrate on the mobile market.
safari has some brilliant javascript engines that they need to focus on and firefox should stop being a monolithic mass of code.

June 9, 2008 at 1:14 am

Sam says:

With regard to, “my specs break in a strange way in IE (no output if a test has failed / suceeded, just a blank page with roman numbers),” my tests are doing this still. How do I upgrade to the release version that has the fix for ie?