{"id":417,"date":"2015-09-27T13:25:26","date_gmt":"2015-09-27T11:25:26","guid":{"rendered":"http:\/\/joost.vunderink.net\/blog\/?p=417"},"modified":"2015-10-12T16:10:25","modified_gmt":"2015-10-12T14:10:25","slug":"checking-log4js-output-in-your-node-js-testsuite","status":"publish","type":"post","link":"https:\/\/joost.vunderink.net\/blog\/2015\/09\/27\/checking-log4js-output-in-your-node-js-testsuite\/","title":{"rendered":"Checking log4js output in your node.js testsuite"},"content":{"rendered":"<p>This post will teach you how to test\u00a0<span class=\"lang:default decode:true crayon-inline \">log4js<\/span>\u00a0 log statements in your code.<\/p>\n<p>You can also find the code below in the `src\/log4js-unittest` dir of my <a href=\"https:\/\/github.com\/joostvunderink\/jvblog-node-samples\">blog code repository<\/a>.<\/p>\n<p>Let&#8217;s say that we have written this simple <a href=\"https:\/\/nodejs.org\/en\/\">node.js<\/a> module, <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0.<\/p>\n<pre class=\"toolbar:1 lang:default decode:true \" title=\"foo.js\">'use strict';\r\n\r\nvar log4js = require('log4js');\r\nvar logger = log4js.getLogger('foo');\r\n\r\nfunction logSomething() {\r\n  logger.info('something');\r\n}\r\n\r\nmodule.exports = {\r\n  logSomething: logSomething\r\n};\r\n<\/pre>\n<p>It would be good to verify that <span class=\"lang:default decode:true crayon-inline \">logSomething()<\/span>\u00a0 actually logs something. Let&#8217;s use this mocha test file skeleton <span class=\"lang:default decode:true crayon-inline \">foo.spec.js<\/span>\u00a0:<\/p>\n<pre class=\"toolbar:1 lang:default decode:true\" title=\"Test file skeleton\">'use strict';\r\n\r\nvar foo = require('.\/foo');\r\n\r\ndescribe('Foo logger', function() {\r\n  it('should log something', function() {\r\n    \/\/ Prepare to detect the log output here\r\n\r\n    foo.logSomething();\r\n\r\n    \/\/ Verify the log output here\r\n  });\r\n});\r\n<\/pre>\n<p>We obviously cannot test <span class=\"lang:default decode:true crayon-inline \">logSomething()<\/span>\u00a0&#8216;s\u00a0return value, so we have to think of something else. Maybe we can somehow stub\u00a0either <span class=\"lang:default decode:true crayon-inline \">log4js.getLogger()<\/span>\u00a0 or <span class=\"lang:default decode:true crayon-inline \">logger.info()<\/span>\u00a0?<\/p>\n<p>Normally, I use <a href=\"http:\/\/sinonjs.org\/\">sinon<\/a> to create <a href=\"http:\/\/sinonjs.org\/docs\/#spies\">spies<\/a>, <a href=\"http:\/\/sinonjs.org\/docs\/#stubs\">stubs<\/a>\u00a0and <a href=\"http:\/\/sinonjs.org\/docs\/#mocks\">mocks<\/a>. With sinon, you can replace object methods or even the objects themselves.<\/p>\n<p>As far as I know, it is not\u00a0possible to modify the <span class=\"lang:default decode:true crayon-inline \">logger<\/span>\u00a0 object inside <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0, because <span class=\"lang:default decode:true crayon-inline \">foo.spec.js<\/span>\u00a0 doesn&#8217;t have access to the scope of <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0. We could add the <span class=\"lang:default decode:true crayon-inline \">logger<\/span>\u00a0 object to the <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 exports, but that doesn&#8217;t seem to be a very elegant solution. I&#8217;d rather not modify <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 itself.<\/p>\n<p>Let&#8217;s see if we can stub <span class=\"lang:default decode:true crayon-inline \">getLogger()<\/span>\u00a0:<\/p>\n<pre class=\"toolbar:1 lang:default decode:true\" title=\"foo.spec.js with getLogger() stubbed\">'use strict';\r\n\r\nvar should = require('should');\r\nvar sinon  = require('sinon');\r\n\r\n\/\/ First require log4js here, so we are in time to stub getLogger.\r\nvar log4js = require('log4js');\r\n\r\n\/\/ Create a spy to see which logger.info calls were done.\r\nvar infoSpy = sinon.spy();\r\n\r\n\/\/ Make sure log4js.getLogger() returns an object containing our spy.\r\nvar getLoggerStub = sinon.stub(log4js, 'getLogger');\r\ngetLoggerStub.returns({ info: infoSpy });\r\n\r\n\/\/ Now we can require foo.js, which will use our getLogger stub.\r\nvar foo = require('.\/foo');\r\n\r\ndescribe('Foo logger', function() {\r\n  it('should log something', function() {\r\n    foo.logSomething();\r\n\r\n    infoSpy.calledOnce.should.equal(true);\r\n    infoSpy.calledWithExactly('something').should.equal(true);\r\n  });\r\n});\r\n<\/pre>\n<p>Does it work?<\/p>\n<pre class=\"toolbar:2 lang:default decode:true\" title=\"Running foo.spec.js\">czapka:~\/src\/blog\/log4js-unittest$ mocha foo.spec.js\r\n\r\n  Foo logger\r\n    \u2713 should log something\r\n\r\n  1 passing (5ms)<\/pre>\n<p>Yes it does!<\/p>\n<p>However, this method relies on being able to stub <span class=\"lang:default decode:true crayon-inline \">log4js.getLogger()<\/span>\u00a0 before <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 is required. What happens if we require <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 before stubbing?<\/p>\n<pre class=\"toolbar:1 lang:default decode:true\" title=\"foo.spec.js with foo.js required earlier\">'use strict';\r\n\r\n\/\/ What if this file (or any other code file or spec file!)\r\n\/\/ requires foo before we can create the getLogger stub?\r\nvar foo = require('.\/foo');\r\n\r\nvar should = require('should');\r\nvar sinon  = require('sinon');\r\nvar log4js = require('log4js');\r\nvar infoSpy = sinon.spy();\r\nvar getLoggerStub = sinon.stub(log4js, 'getLogger');\r\ngetLoggerStub.returns({ info: infoSpy });\r\n\r\ndescribe('Foo logger', function() {\r\n  it('should log something', function() {\r\n    foo.logSomething();\r\n\r\n    infoSpy.calledOnce.should.equal(true);\r\n    infoSpy.calledWithExactly('something').should.equal(true);\r\n  });\r\n});\r\n<\/pre>\n<p>Unfortunately, this fails:<\/p>\n<pre class=\"toolbar:2 lang:default decode:true\">czapka:~\/src\/blog\/log4js-unittest$ mocha foo.spec.js\r\n\r\n  Foo logger\r\n[2015-09-27 12:49:55.284] [INFO] foo - something\r\n    1) should log something\r\n\r\n  0 passing (7ms)\r\n  1 failing\r\n\r\n  1) Foo logger should log something:\r\n\r\n      AssertionError: expected false to be true<\/pre>\n<p>The reason that this fails, is that node.js uses a module cache. The first time a module is required, it is loaded; any followup <span class=\"lang:default decode:true crayon-inline\">require()<\/span>\u00a0\u00a0calls simply return the loaded module from node.js&#8217;s module cache.<\/p>\n<p>This makes the test very fragile. If any other module requires <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 before this test is run, the <span class=\"lang:default decode:true crayon-inline \">logger<\/span>\u00a0 object inside <span class=\"lang:default decode:true crayon-inline \">foo.js<\/span>\u00a0 has already been created before the <span class=\"lang:default decode:true crayon-inline \">getLogger()<\/span>\u00a0 stub can be used.<\/p>\n<p>What else is there to try?<\/p>\n<p>We could add an <a href=\"https:\/\/github.com\/nomiddlename\/log4js-node\/wiki\/Appenders\">appender<\/a> to <span class=\"lang:default decode:true crayon-inline \">log4js<\/span>\u00a0 that captures any logged lines for verification afterward. Since there did not seem to be such an appender yet, I decided to <a href=\"https:\/\/www.npmjs.com\/package\/log4js-test-appender\">create one<\/a>.<\/p>\n<p>This is how to use it:<\/p>\n<pre class=\"toolbar:1 lang:default decode:true\" title=\"foo.spec.js using the log4js test appender\">'use strict';\r\n\r\n\/\/ We can now load foo.js at any point, because we are no longer\r\n\/\/ stubbing log4js.getLogger().\r\nvar foo    = require('.\/foo');\r\nvar should = require('should');\r\n\r\nvar testAppender = require('log4js-test-appender');\r\ntestAppender.init();\r\n\r\ndescribe('Foo logger', function() {\r\n  it('should log something', function() {\r\n    foo.logSomething();\r\n\r\n    var logEvents = testAppender.getLogEvents();\r\n    logEvents.should.have.length(1);\r\n    logEvents[0].level.levelStr.should.equal('INFO');\r\n    logEvents[0].data[0].should.equal('something');\r\n  });\r\n});\r\n<\/pre>\n<p>There we go, a robust, working test.<\/p>\n<p>The API of <span class=\"lang:default decode:true crayon-inline \">log4js-test-appender<\/span>\u00a0 is not very elegant yet, so if you have any suggestions for improvement, feel free to <a href=\"https:\/\/github.com\/joostvunderink\/node-log4js-test-appender\">comment or create a merge request<\/a>! And if you have an alternative suggestion to test <span class=\"lang:default decode:true crayon-inline \">log4js<\/span>\u00a0 log statements, I&#8217;m all ears.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post will teach you how to test\u00a0log4js\u00a0 log statements in your code. You can also find the code below in the `src\/log4js-unittest` dir of my blog code repository. Let&#8217;s say that we have written this simple node.js module, foo.js\u00a0. &hellip; <a href=\"https:\/\/joost.vunderink.net\/blog\/2015\/09\/27\/checking-log4js-output-in-your-node-js-testsuite\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[176,177,10],"tags":[240,238,239,242,232,228,226,243,231,234,233,236,241,237,235],"class_list":["post-417","post","type-post","status-publish","format-standard","hentry","category-javascript","category-node-js","category-software-development","tag-log-output","tag-log4js","tag-logger","tag-mocha","tag-mock","tag-node","tag-node-js","tag-npm","tag-sinon","tag-spy","tag-stub","tag-test","tag-test-log-output","tag-test-suite","tag-unit-test"],"_links":{"self":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/417"}],"collection":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/comments?post=417"}],"version-history":[{"count":16,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/417\/revisions"}],"predecessor-version":[{"id":437,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/417\/revisions\/437"}],"wp:attachment":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/media?parent=417"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/categories?post=417"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/tags?post=417"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}