{"id":509,"date":"2016-03-06T17:39:37","date_gmt":"2016-03-06T16:39:37","guid":{"rendered":"http:\/\/joost.vunderink.net\/blog\/?p=509"},"modified":"2016-03-14T22:27:46","modified_gmt":"2016-03-14T21:27:46","slug":"automated-software-testing-part-5-spying","status":"publish","type":"post","link":"https:\/\/joost.vunderink.net\/blog\/2016\/03\/06\/automated-software-testing-part-5-spying\/","title":{"rendered":"Automated software testing part 5: spying"},"content":{"rendered":"<p>Previous: <a href=\"http:\/\/joost.vunderink.net\/blog\/2016\/02\/28\/automated-software-testing-part-4-combining-similar-tests\/\">part 4<\/a>\u00a0&#8211; Next: <a href=\"http:\/\/joost.vunderink.net\/blog\/2016\/03\/14\/automated-software-testing-part-6-stubbing\/\">part 6<\/a><\/p>\n<p>You can also find the code below in the <span class=\"lang:default decode:true crayon-inline \">src\/unit-test-3<\/span>\u00a0\u00a0dir of my <a href=\"https:\/\/github.com\/joostvunderink\/jvblog-node-samples\">blog code repository<\/a>.<\/p>\n<p>Spying sounds really cool.<\/p>\n<div id=\"attachment_510\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/03\/mission-impossible.jpg\" rel=\"attachment wp-att-510\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-510\" class=\"size-medium wp-image-510\" src=\"http:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/03\/mission-impossible-300x204.jpg\" alt=\"A spy hanging from a wire just above the floor\" width=\"300\" height=\"204\" srcset=\"https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/03\/mission-impossible-300x204.jpg 300w, https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/03\/mission-impossible-768x523.jpg 768w, https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/03\/mission-impossible.jpg 1000w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><p id=\"caption-attachment-510\" class=\"wp-caption-text\">A real spy<\/p><\/div>\n<p>I have to disappoint you. Unfortunately (or maybe not), <a href=\"http:\/\/angular-tips.com\/blog\/2014\/03\/introduction-to-unit-test-spies\/\">spying in the world of unit testing<\/a> is nowhere as exciting as spying in real life. At least, that&#8217;s what I think. Maybe the view we non-spies have on spying, created by all these <a href=\"http:\/\/www.missionimpossible.com\/\">films<\/a> and <a href=\"http:\/\/www.bbc.co.uk\/programmes\/b006mf4b\">series<\/a>, is completely incorrect. Who knows, maybe unit test spying <em>is<\/em> more exciting than real world spying.<\/p>\n<p>Anyway, spying. <strong>A test spy is a way to verify that a specific call, method or function has been called correctly<\/strong>.<\/p>\n<p>Sounds pretty vague, right? Let&#8217;s look at some code to make that more concrete.<\/p>\n<pre class=\"lang:js decode:true\" title=\"shop.js\">\/\/ shop.js\r\n\r\nvar myLogger = require('.\/mylogger');\r\n\r\nfunction calculateTotal(items) {\r\n  var totalPrice = 0;\r\n  var totalItems = 0;\r\n\r\n  items.forEach(function(item) {\r\n    totalItems += item.count;\r\n    totalPrice += item.count * item.price;\r\n  });\r\n\r\n  if (totalPrice &gt; 1000) {\r\n    myLogger.log('A probably order of ' + totalPrice + ' is being considered!');\r\n  }\r\n\r\n  return {\r\n    totalPrice: totalPrice,\r\n    totalItems: totalItems,\r\n  };\r\n}\r\n\r\nmodule.exports = {\r\n  calculateTotal: calculateTotal\r\n};\r\n<\/pre>\n<p>This is a module for your web shop, with a function in it to calculate the total price. Of course you are going to be very excited every time a large order is about to happen, so that&#8217;s why you decided to log every calculation where the total is over 1000.<\/p>\n<p>For now, you&#8217;re just logging this to console, using <span class=\"lang:default decode:true crayon-inline \">mylogger.js<\/span>\u00a0:<\/p>\n<pre class=\"lang:js decode:true \" title=\"mylogger.js\">\/\/ mylogger.js\r\n\r\nfunction log(message) {\r\n  console.log(message);\r\n}\r\n\r\nmodule.exports = {\r\n  log: log\r\n};\r\n<\/pre>\n<p>How can we test whether the reporting mechanism works?<\/p>\n<p>We could try to fiddle around with capturing <span class=\"lang:default decode:true crayon-inline \">STDOUT<\/span>\u00a0 or overwriting <span class=\"lang:default decode:true crayon-inline \">console.log<\/span>\u00a0. That&#8217;s certainly an option, but it&#8217;s a bit crude. Besides, if we change the <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 function to send an email instead of writing to console, it won&#8217;t work anymore.<\/p>\n<p>What are we really interested in? Well, we want to know whether the <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 function has been called by calculateTotal. We don&#8217;t need the actual <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 function to be called &#8211; this might even cause problems, if <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 would send an email or write to a database.<\/p>\n<p>Here, a spy comes in handy. We replace\u00a0the <span class=\"lang:default decode:true crayon-inline \">myLogger.log<\/span>\u00a0 call by a spy. This has 2 advantages:<\/p>\n<ol>\n<li>We can see exactly with which arguments <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 was called<\/li>\n<li>The actual behaviour of <span class=\"lang:default decode:true crayon-inline \">log<\/span>\u00a0 is not executed<\/li>\n<\/ol>\n<p>Here&#8217;s one way of doing it, with the excellent <a href=\"http:\/\/sinonjs.org\/\">sinon.js<\/a> library:<\/p>\n<pre class=\"lang:js decode:true \" title=\"shop.spec.js\">\/\/ shop.spec.js\r\n\r\nrequire('should');\r\nvar sinon = require('sinon');\r\n\r\nvar shop = require('.\/shop');\r\nvar myLogger = require('.\/mylogger');\r\n\r\ndescribe('calculateTotal', function() {\r\n  it('should log a happy message for a large total', function() {\r\n    var items = [\r\n      {\r\n        price: 200,\r\n        count: 4,\r\n      },\r\n      {\r\n        price: 300,\r\n        count: 2,\r\n      }\r\n    ];\r\n\r\n    var logSpy = sinon.spy(myLogger, 'log');\r\n\r\n    shop.calculateTotal(items);\r\n\r\n    logSpy.calledOnce.should.be.true;\r\n    logSpy.getCall(0).args[0].should.equal('An order of 1400 is being considered!')\r\n  });\r\n});<\/pre>\n<p>Another situation where a spy could come in handy, is when you are <strong>calling a function that expects a callback<\/strong>. In that case, you can just create a spy with <span class=\"lang:default decode:true crayon-inline \">sinon.spy()<\/span>\u00a0 and pass that as callback. Afterwards, you can verify that the callback was called with the right arguments. You can find an <a href=\"http:\/\/sinonjs.org\/docs\/#spies\">example<\/a> in the sinon.js documentation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Previous: part 4\u00a0&#8211; Next: part 6 You can also find the code below in the src\/unit-test-3\u00a0\u00a0dir of my blog code repository. Spying sounds really cool. I have to disappoint you. Unfortunately (or maybe not), spying in the world of unit &hellip; <a href=\"https:\/\/joost.vunderink.net\/blog\/2016\/03\/06\/automated-software-testing-part-5-spying\/\">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":[10,272],"tags":[242,226,231,285,234,284,273,235],"class_list":["post-509","post","type-post","status-publish","format-standard","hentry","category-software-development","category-testing-software-development","tag-mocha","tag-node-js","tag-sinon","tag-spies","tag-spy","tag-test-spy","tag-testing","tag-unit-test"],"_links":{"self":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/509"}],"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=509"}],"version-history":[{"count":2,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/509\/revisions"}],"predecessor-version":[{"id":516,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/509\/revisions\/516"}],"wp:attachment":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/media?parent=509"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/categories?post=509"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/tags?post=509"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}