Checking for Global Variable Leaks in Unit Tests

Javascript is notorious for making it easy to leak global variables. For example, if you forget to use the var keyword when declaring a variable i:

function f(){
    i = 1
}
f()

The same thing happens with variables declared in the for clause of for loops:

for (i = 0; i < array.length; i++){
    ...
}

i becomes a global variable rather than staying within the function scope. I found that some beginners in Javascript often make this mistake. I once had to work with a codebase which had numerous such errors and I tasked myself with fixing these "global variable leaks".

I decided that I would write unit tests(with Jasmine) and I would put in code in the setup and teardown phases on the tests to track if any global variables were leaked during the execution of each test:

describe('stuff', function(){
    var globalsBefore = {}
    beforeEach(function(){
        for (var key in window)
            globalsBefore[key] = true
    })
    afterEach(function(){
        var leaked = []
        for (var key in window)
            if (!key in globalsBefore)
                leaked.push(key)
        if (leaked.length > 0)
            throw new Error('Leaked global variables: [' + leaked.join(', ') + ']')
    })
})

The above code checks takes an inventory of all of the global variables that are present beforeEach test, and then rechecks them afterEach test to see if any new variable was introduced, and throws an exception to fail the test if that was indeed the case.

It turned out that this simplistic method didn't always work out because sometimes, a test actually does want to create global variables legitimately. For example some browsers make iframes on the pages directly indexable on the window object:

window[0] // this gives you the first iframe in the page!

Well, I guess we can specify a list of global variable names to ignore:

var globalsToIgnore = ['0']

and then afterEach test we'll check that array as well:

for (var key in window)
    if (!key in globalsBefore && globalsToIngnore.indexOf(key) === -1)
        leaked.push(key)

Note that Array's indexOf method is not supported on older IE's.

Conclusion

Using this method, I found all global variable leaks in the codebase once I wrote sufficient tests. It will also prevent future incidents.

blog comments powered by Disqus