Skip to content

Instantly share code, notes, and snippets.

@jsilvestre
Created January 28, 2015 10:10
Show Gist options
  • Save jsilvestre/e53b61d6ce0d9d777d59 to your computer and use it in GitHub Desktop.
Save jsilvestre/e53b61d6ce0d9d777d59 to your computer and use it in GitHub Desktop.
Demonstration of the singleton testing problem
singleton = require '../lib/singleton_lib'
module.exports.get = (req, res, next) ->
result = singleton.init()
res.send 200, result.join(',')
describe "Controller using singleton lib", ->
describe ".get route", ->
it "When get route is called", ->
# http call to the route
it "Then I should have the correct result", ->
# check the result
body.should.equal "1,2"
# Currently, this is how we do a singleton
# example: https://github.com/cozy/cozy-home/blob/master/server/lib/autostop.coffee
privateVariable = []
module.exports.function1 = ->
# do something with applicationTimeout
privateVariable.push 1
module.exports.function2 = ->
# do something else with applicationTimeout
privateVariable.push 2
module.exports.init = ->
function1()
function2()
return privateVariable
describe "Singleton Lib", ->
describe ".init()", ->
before ->
singleton = require '../lib/singleton_lib'
# mock function 1
# mock function 2
it "When I call .init()", ->
result = singleton.init()
# function1.should.be.called
# function2.should.be.called
result.should.be [1, 2]
The problem is that if you run each test separately, it will work, but running them both will trigger an error: during the singleton test, it will be okay, but the result of the controller test will be "1,2,1,2" because `privateVariable` is kept, because it's how it's supposed to work.
But for testing, we want isolation, and we can't make test in isolation in that case. Testing is about defining an initial state, perform something and check the result. With this kind of singleton, you can't define the initial state.
There are two solutions:
1°) we use a module that will override require during the tests
2°) we improve make to design testable
I don't like 1°) because overring require can't be a good idea.
For solution 2°), using a class solves the problem easily because you export the class (so you can instantiate wherever you want = it's isolated) and an instance of that class that will be the singleton.
That way you can unit test your singleton and eventually write tests that use the singleton without corrupting its state.
On a side note, the singleton should also be mocked when testing part of the code that uses it to have a better control on the output (e.g. dealing with error cases easily), but I understand that is not always possible, especially in legacy code. The idea is to make them unit testable.
@frankrousseau
Copy link

Another option is to add a reset function to the module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment