Goal: Unit testing the javascript codeIn todays modern web applications, unit testing all your layers should be a goal. Often a developer unit tests the C#-code, so why only write unit tests for the server code, when the client code (such as javascript) is just as important? The end user uses both, and if there are custom logic in your javascript the code should definately have its own unit tests. In this article I will explain my own little adventure into looking at the possibilities I have to unit test my javascript code.
Initial spinI have trying to resolve the issue of how to proper unit test javascript code in a MVC application the latest days. Basically I initially started out with the idea of definining which javascript code I would like to load and which unit test I would like to run. I ended up creating a test solution which was essentially working, but I did not like the end result. The good part was that I was using the
QUnit unit testing framework that much of jQuery is based upon. I basically wrote the necessary code to define the javascript and the unit tests to run and I then looped through the list to launch off the browser to display results from QUnit. Yes, this works and yes it uses the elegant QUnit framework - but there are several negative sides of this
browser launcher approach. The really negative part is that these browser launched tests needs non-automatic supervision. The tester must inspect the results in the browser and there is really a lot of dependendency going on. We need a working browser and web server, so this is not a general unit test. I was striving to find the solution, and in general I wanted to use MS Test, i.e. I wanted to define ordinary unit tests that were "javascript flavored". Before venturing too deep into this seemingly mess, I discovered that somebody else already had thought of this.
Stephen Walter to the rescueIn his
blog article,
Stephen Walter explains how to do the proper unit test
with MsTest, which is just what I wanted. Best of all, since we now can use MsTest this will support automatic running of unit tests and fits nicely into MSBuild and the rest of the test infrastructure. Finally the javascript code can be put under control! What I then did was read through Stephen Walter's article and test out his complementary solution. It worked perfectly out of the box, and I do not see much changes that needs to be done to make this suit perfect into MVC 3 as Stephen Walter already shows the solution in such an application.
Summing up the article of Steven WalterI just want to confirm to the rest of the interested party that Steven Walter's solution to unit tests in javascript is working perfectly. I will present his solution in summary form next. First of all, the unit test project added to the MVC
project that should have tests for javascript must have a reference to the Microsoft Script Control. This is an ActiveX COM component. The addon can be downloaded using the link next:
Microsoft Script Control 1.0. Once this DLL has been added to the unit test project, you want to add a
unit test. This will not be a
basic unit test, as this kind of test does not have a
TestContext, which will be needed. Second, add a folder called helpers, and add Stephen Walter's helper class for running the javascript code. The javascript code and javascript unit test will not run in a browser envrironment, but use the ScriptControl class. This will behind the scenes actually use the JScript engine which means that the browser in effect we test is Internet Explorer 8. This will actually suit our needs, since the loading of the additional libraries such as jQuery to support cross browser support should also be needed. The plumbing code to set up the ScriptControl with the correct .js library files I would suggest is set up in the TestInitialize method of your unit test, and it should call a common method, preferably also in Helpers folder to load up the "default" jquery javascript files once javascript code that uses jQueryis to be tested. Beware of the ordering of loading the js files. The js files should be as default be built using build type content (we will load up these files using "loose files"). The path pointing to the js files should point to the javascript files in the Content/Scripts folder. In general, javascript that should be tested and be available for the views should in general reside in the Content/Scripts folder for a MVC application. The tests written in javascript is in Steven Walters example put in a folder called JavascriptTests in the test project. This looks like a good practice. In general, the folder structure under Content/Scripts should somehow match the folder structure under JavascriptTests, once one gets more javascript tests added.
In Steven Walther's article there is a specific step of setting up the test settings for the javascript code to deploy a folder for the javascript code during the execution of the test. This step should be carefully read, since the tests needs to find the javascript files during the testexecution. Basically, a .testsetting file needs to be added to the solution to make this possible, so I just refer to the article.
Let me present the helper class which Steven Walter created for instantiating and initializing the ScriptControl instance.
public class JavaScriptTestHelper : IDisposable {
private ScriptControl _sc;
private TestContext _context;
///
/// You need to use this helper with Unit Tests and not
/// Basic Unit Tests because you need a Test Context
///
/// Unit Test Test Context
public JavaScriptTestHelper(TestContext testContext) {
if (testContext == null) {
throw new ArgumentNullException("TestContext");
}
_context = testContext;
_sc = new ScriptControl();
_sc.Language = "JScript";
_sc.AllowUI = false;
}
///
/// Load the contents of a JavaScript file into the
/// Script Engine.
///
/// Path to JavaScript file
public void LoadFile(string path) {
var fileContents = File.ReadAllText(path);
_sc.AddCode(fileContents);
}
///
/// Pass the path of the test that you want to execute.
///
/// JavaScript function name
public void ExecuteTest(string testMethodName) {
dynamic result = null;
try {
result = _sc.Run(testMethodName, new object[] { });
} catch {
var error = ((IScriptControl)_sc).Error;
if (error != null) {
var description = error.Description;
var line = error.Line;
var column = error.Column;
var text = error.Text;
var source = error.Source;
if (_context != null) {
var details = String.Format("{0} \r\nLine: {1} Column: {2}", source, line, column);
_context.WriteLine(details);
}
}
throw new AssertFailedException(error.Description);
}
}
public void Dispose() {
_sc = null;
}
The JavascriptTestHelper class of Steven Walter basically news up a ScriptControl class and contains two methods to load a javscript file (.js) and then exceute the test. Of course, several javascript files can be added. Note that the ScriptControl instance which is a COM object needs to be nulled when disposing the object.
Ok, so this is the class that will be used when writing the [TestMethod] in C# for the javascript test. Basically we must arrange the test using this testhelper to load up the .js files, and then use the test helper to execute the javascript test. The test method name must be added also to the ScriptController instance before starting up.
Every javascript unit test will of course in essence be written in Javascript, the C# based unit test is just a wrapper, the really interesting part is as always in Javscript when it comes to the client code. So each javascript class will then refer to a utility library. Steven Walther gives a very simple example of such a library, which is nothing more than providing a AreEqual function. The unit test written in javascript will then refer to this "library", using the ///
syntax. This library will be in the base path of he JavascriptTests folder for now. The path must point to this JavascriptUnitTestLibrary.js file. The AreEqual method is really simple and the addition of more methods to support different errors should be a overcoming task. I will myself try out addind more such methods. The sad part is that this code does not use QUnit to support more function that Steven Walter's AreEqual metod, but this method covers very much of the needed functionality. I will soon add more examples of unit testing functions soon into this file.
Here is the JavascriptUnitTestLibrary.js file:
var assert = {
areEqual: function (expected, actual, message) {
if (expected !== actual) {
throw new Error("Expected value " + expected
+ " is not equal to " + actual + ". " + message);
}
}
};
The little library does nothing more than define an object with a single method areEqual which will throw an error. The script controller will in the helper class
catch this error and then rethrow it into an AssertFailedException, which MSTest will then recognize and we are getting our fancy detection of errors in Javascript that MSTest will see! Elegant!
So the only thing next when it comes to the javascript unit test is to write the test and use this utility library. Lets add a new test for demonstrational purposes to the addTwoNumbers method Steven Walther was presenting in this article, the factorial of a number, let's put this additional code to MathTest.js (the reference line at the top should only be listed once).
MathTest.js:
///
function testFactorial() {
var result = factorial(5);
assert.areEqual(120, result, "factorial did not return righ value!");
}
This test will then assert that the factorial of five is equal to 120 (check with your calculator!). The MathTest.js file is added to the JavascriptTests folder.
Ok, so next up is then writing the unit test, add a new unit test to the test project
and then write the following:
[TestMethod]
public void TestFactorial()
{
var jsHelper = new JavaScriptTestHelper(this.TestContext);
jsHelper.LoadFile("JavaScriptUnitTestFramework.js");
jsHelper.LoadFile(@"..\..\..\MvcApplication1\Scripts\Math.js");
jsHelper.LoadFile("MathTest.js");
jsHelper.ExecuteTest("testFactorial");
}
If you can't understand why the code in the JavascriptTests are references as seemingly being in the same folder as the unit test (which is one folder up), this is handled by the testsettings file added previously. Refer to Steven Walthers article.
The test method in my file, UnitTest1.cs is then newing up the test helper class
and make note, passes in the testcontext of my unit test, which is why a basic unit
test will not suffice in this case. Then the ordering of loading the js files is
important. First the unit test framework js file is added. Then the javascript
code is added which will be tested. Next up the javascript code that contains the
javascript based test is added. If your javascript code uses a lot of jQuery libraries, additional js files should be added before the user provided javascript
code. The ordering is as previously mentioned important, and the paths should be correct. Basically one wants to go up three levels of the current directory and then go into the Scripts or Content/Scripts folder to the target .js file. I really suggest that the Scripts folder should be the place that custom javascript is added and that the .js files that the user creates should be in sub folders here. The base directory of the Scripts folder should only contain the jquery libraries and similar, not the user based code, as this is not very scalable as the web project grows..
Finally the ExecuteTest is called and the parameter provided is the name of the javascript test.
I also of course had to add the factorial function in the Math.js. This code looks like this:
function factorial(n) {
if (n > 1) {
return n * factorial(n - 1);
}
else {
return 1;
}
}
This factorial function uses recursion to return the factorial of a number. The code is almost the same as a C# based function for the factorial.
Then I just ran the unit test with Ctrl+R and Ctrl+T and saw it pass in the test results. Great stuff Steven Walther! I believe this is the correct way to do unit tests in javascript, i.e. not using QUnit.
The next obvious task is to implement more code to Steven Walther's JavascriptUnitTestLibrary.js file. This should be a near future task I will look into.
I have looked at other parts of unit testing MVC applications. Unit testing controllers are actually very good supported, as these are ordinary classes, which requests can be mocked. The unit testing of views is possible using the RazorSingleFileGenerator or RazorGenerator extension (although many means that unit testing a MVC view should be avoided).
One open issue is how to unit test a MVC 3 Razor helper, I will look into this perhaps using RazorSingleFileGenerator extension in the near future..