{"id":239,"date":"2013-01-27T15:10:40","date_gmt":"2013-01-27T14:10:40","guid":{"rendered":"http:\/\/benjiweber.co.uk\/blog\/?p=239"},"modified":"2013-01-27T15:31:18","modified_gmt":"2013-01-27T14:31:18","slug":"javascript-tests-with-junit","status":"publish","type":"post","link":"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/","title":{"rendered":"JavaScript tests with JUnit"},"content":{"rendered":"<p class=\"lead\">This weekend I have been playing with <a href=\"https:\/\/blogs.oracle.com\/nashorn\/entry\/welcome_to_the_nashorn_blog\">Nashorn<\/a>, the new JavaScript engine coming in Java 8.<\/p>\n<p>As an exercise I implemented a JUnit runner for JavaScript unit tests using Nashorn. Others have implemented similar wrappers, we even have one at work. None of the ones I have found do everything I want, and it was a fun project.<\/p>\n<p><img src=\"http:\/\/benjiweber.co.uk\/testoutput1.png\" title=\"Nashorn Test Output\"\/><\/p>\n<p>Because the JavaScript tests are JUnit tests they &#8220;Just work&#8221; with existing JUnit tools like Eclipse and as part of your build with ant\/maven. The Eclipse UI shows every test function and a useful error trace (Line numbers only work with Nashorn).<\/p>\n<p>There are also lots of reasons you wouldn&#8217;t want to do this &#8211; your tests have to work in a very Java-y way, and you miss out on great features of JavaScript testing tools. There&#8217;s also no DOM, so you may end up having to stub a lot if you are testing code that interacts with the DOM. This can be a good thing and encourage you not to couple code to the DOM.<\/p>\n<p>Here&#8217;s what a test file looks like. <\/p>\n<pre lang=\"JavaScript\">\r\ntests({\r\n\tthisTestShouldPass : function() {\r\n\t\tconsole.log(\"One == One\");\r\n\t\tassert.assertEquals(\"One\",\"One\");\r\n\t},\r\n\t\r\n\tthisTestShouldFail : function() {\r\n\t\tconsole.log(\"Running a failing test\");\r\n\t\tassert.fail();\r\n\t},\r\n\t\r\n\ttestAnEqualityFail : function() {\r\n\t\tconsole.log(\"Running an equality fail test\");\r\n\t\tassert.assertEquals(\"One\", \"Two\");\r\n\t},\r\n\t\r\n\tobjectEquality : function() {\r\n\t\tvar a = { foo: 'bar', bar: 'baz' };\r\n\t\tvar b = a;\r\n\t\tassert.assertEquals(a, b);\r\n\t},\r\n\t\r\n\tintegerComparison : function() {\r\n\t\tjsAssert.assertIntegerEquals(4, 4);\r\n\t},\r\n\t\r\n\tfailingIntegerComparison : function() {\r\n\t\tjsAssert.assertIntegerEquals(4, 5);\r\n\t}\r\n});\r\n<\/pre>\n<p>You can easily extend the available test tools using either JavaScript or Java. In order to show the failure reason in JUnit tools you just need to ensure you throw a <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/AssertionError.html\">java AssertionError<\/a> at some point.<\/p>\n<p>The tests themselves are executed from Java by returning a list of <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Runnable.html\">Runnables<\/a> from JavaScript. <\/p>\n<pre lang=\"JavaScript\">\r\nvar tests = function(testObject) {\r\n\tvar testCases = new java.util.ArrayList();\r\n\tfor (var name in testObject) {\r\n\t\tif (testObject.hasOwnProperty(name)) {\r\n\t\t\ttestCases.add(new TestCase(name,testObject[name]));\r\n\t\t}\r\n\t}\r\n\treturn testCases;\r\n};\r\n<\/pre>\n<p>Where TestCase is a Java class with a constructor like:<\/p>\n<pre lang=\"Java\">\r\n  public TestCase(String name, Runnable testCase) {\r\n<\/pre>\n<p>Nashorn\/Rhino will both convert a JavaScript function to a Runnable automatically :)<\/p>\n<p>On the Java side we just create a Test Suite that lists the JavaScript files containing our tests, and tell JUnit we want to run it with a custom Runner.<\/p>\n<pre lang=\"Java\">\r\n@Tests({\r\n\t\"ExampleTestOne.js\", \r\n\t\"ExampleTestTwo.js\",\r\n\t\"TestFileUnderTest.js\"\r\n})\r\n@RunWith(JSRunner.class)\r\npublic class ExampleTestSuite {\r\n\t\r\n}\r\n<\/pre>\n<p>Our <a href=\"https:\/\/github.com\/benjiman\/junit-js\/blob\/master\/src\/main\/java\/uk\/co\/benjiweber\/junitjs\/JSRunner.java\">Runner<\/a> has to create a heirarchy of JUnit <a href=\"http:\/\/junit.sourceforge.net\/javadoc\/org\/junit\/runner\/Description.html\">Descriptions<\/a>; Suite -> JS Test File -> JS Test Function<\/p>\n<p>The <a href=\"https:\/\/github.com\/benjiman\/junit-js\/blob\/master\/src\/main\/java\/uk\/co\/benjiweber\/junitjs\/JSRunner.java\">Runner<\/a> starts up a Nashorn or Rhino script engine, evaluates the JavaScript files to get a set of TestCases to run, and then executes them.<\/p>\n<pre lang=\"Java\">\r\nScriptEngineManager factory = new ScriptEngineManager();\r\nScriptEngine nashorn = factory.getEngineByName(\"nashorn\");\r\n\t\t\r\nif (nashorn != null) return nashorn;\r\n\/\/ Load Rhino if no nashorn.\r\n<\/pre>\n<p>You can quickly implement stubbing that also integrates with your Java JUnit tools.<\/p>\n<p><img src=\"http:\/\/benjiweber.co.uk\/testoutput_stubs.png\" title=\"Nashorn Test Output\"\/><\/p>\n<p>Here&#8217;s the test code from the above screenshot.<\/p>\n<pre lang=\"JavaScript\">\r\n\r\nload(\"src\/main\/java\/uk\/co\/benjiweber\/junitjs\/examples\/FileUnderTest.js\");\r\n\r\nvar stub = newStub();\r\nunderTest.collaborator = stub;\r\n\r\ntests({\t\r\n\tdoesSomethingImportant_ThisTestShouldFail: function() {\r\n\t\tunderTest.doesSomethingImportant();\r\n\t\t\r\n\t\tstub.assertCalled({\r\n\t\t\tname: 'importantFunction',\r\n\t\t\targs: ['wrong', 'args']\r\n\t\t});\r\n\t},\r\n\tdoesSomethingImportant_ShouldDoSomethingImportant: function() {\r\n\t\tunderTest.doesSomethingImportant();\r\n\t\t\r\n\t\tstub.assertCalled({\r\n\t\t\tname: 'importantFunction',\r\n\t\t\targs: ['hello', 'world']\r\n\t\t});\r\n\t}\r\n});\r\n\r\n<\/pre>\n<p>To implement the stub you can use __noSuchMethod__ to capture interactions and store them for later assertions.<\/p>\n<pre lang=\"JavaScript\">\r\nvar newStub = function() {\r\n\treturn \t{\r\n\t\tcalled: [],\r\n\t\t__noSuchMethod__:  function(name, arg0, arg1, arg2, arg3, arg4, arg5) {\r\n\t\t\tvar desc = {\r\n\t\t\t\tname: name,\r\n\t\t\t\targs: []\r\n\t\t\t};\r\n\t\t\tvar rhino = arg0.length && typeof arg1 == \"undefined\";\r\n\t\t\t\r\n\t\t\tvar args = rhino ? arg0 : arguments;\r\n\t\t\tfor (var i = rhino ? 0 : 1; i < args.length; i++){\r\n\t\t\t\tif (typeof args[i] == \"undefined\") continue;\r\n\t\t\t\tdesc.args.push(args[i]);\r\n\t\t\t}\r\n\t\t\tthis.called.push(desc);\r\n\t\t},\r\n\t\t\r\n\t\tassertCalled: function(description) {\r\n\t\t\t\r\n\t\t\tvar fnDescToString = function(desc) {\r\n\t\t\t\treturn desc.name + \"(\"+ desc.args.join(\",\") +\")\";\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tif (this.called.length < 1) assert.fail('No functions called, expected: ' + fnDescToString(description));\r\n\r\n\t\t\tfor (var i = 0; i < this.called.length; i++) {\r\n\t\t\t\tvar fn = this.called[i];\r\n\t\t\t\tif (fn.name == description.name) {\r\n\t\t\t\t\tif (description.args.length != fn.args.length) continue;\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var j = 0; j < description.args.length; j++) {\r\n\t\t\t\t\t\tif (fn.args[j] == description.args[j]) return;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tassert.fail('No matching functions called. expected: ' + \r\n\t\t\t\t\t'<' + fnDescToString(description) + \")>\" +\r\n\t\t\t\t\t' but had ' +\r\n\t\t\t\t\t'<' + this.called.map(fnDescToString).join(\"|\") + '>'\r\n\t\t\t);\r\n\t\t}\r\n\t};\r\n};\r\n<\/pre>\n<p><a href=\"https:\/\/github.com\/benjiman\/junit-js\">The code is on Github<\/a><\/p>\n<p>It is backwards compatible with Rhino (JavaScript Scripting engine in current and old versions of Java). Most things seem just as possible in Rhino, but it&#8217;s easier to work with Nashorn due to its meaningful error messages. <\/p>\n<p>You can also run using Nashorn on Java7 using a <a href=\"https:\/\/bitbucket.org\/ramonza\/nashorn-backport\">backport<\/a> and adding nashorn to the bootclasspath with -Xbootclasspath\/a:$NASHORN_HOME\/dist\/nashorn.jar<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This weekend I have been playing with Nashorn, the new JavaScript engine coming in Java 8. As an exercise I implemented a JUnit runner for JavaScript unit tests using Nashorn. Others have implemented similar wrappers, we even have one at work. None of the ones I have found do everything I want, and it was&#8230;  <a href=\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/\" class=\"more-link\" title=\"Read JavaScript tests with JUnit\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[8],"tags":[23,16],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"JavaScript tests with JUnit - Benji&#039;s Blog\" \/>\n<meta property=\"og:description\" content=\"This weekend I have been playing with Nashorn, the new JavaScript engine coming in Java 8. As an exercise I implemented a JUnit runner for JavaScript unit tests using Nashorn. Others have implemented similar wrappers, we even have one at work. None of the ones I have found do everything I want, and it was... Read more &raquo;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/\" \/>\n<meta property=\"og:site_name\" content=\"Benji&#039;s Blog\" \/>\n<meta property=\"article:published_time\" content=\"2013-01-27T14:10:40+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2013-01-27T14:31:18+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/benjiweber.co.uk\/testoutput1.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#website\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/\",\"name\":\"Benji&#039;s Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/benjiweber.co.uk\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"http:\/\/benjiweber.co.uk\/testoutput1.png\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/#webpage\",\"url\":\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/\",\"name\":\"JavaScript tests with JUnit - Benji&#039;s Blog\",\"isPartOf\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/#primaryimage\"},\"datePublished\":\"2013-01-27T14:10:40+00:00\",\"dateModified\":\"2013-01-27T14:31:18+00:00\",\"author\":{\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#\/schema\/person\/45ecb36b51f4ce99e6929d2d31ca5c09\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/benjiweber.co.uk\/blog\/2013\/01\/27\/javascript-tests-with-junit\/\"]}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#\/schema\/person\/45ecb36b51f4ce99e6929d2d31ca5c09\",\"name\":\"benji\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/benjiweber.co.uk\/blog\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/05fb47b31a0b329e1b790074a9b624ef?s=96&d=mm&r=g\",\"caption\":\"benji\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/239"}],"collection":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/comments?post=239"}],"version-history":[{"count":14,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/239\/revisions"}],"predecessor-version":[{"id":259,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/posts\/239\/revisions\/259"}],"wp:attachment":[{"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benjiweber.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}