Whether you’re an Ajax programmer and you want to use JScript in ASP .NET on the server side, or you just want to use a standard language on the server, you will probably be surprised by some new problems you encounter with functions you define on the global object in JScript .NET. Microsoft has created a more modern form of JavaScript for .NET than they have in their browser. This form allows objects to either strongly typed or dynamically typed. The difficulty is determining when the dynamic rules still apply and when static rules take over. The default form of JScript .NET in ASP .NET (server-side) treats functions written on the Global object as as public static read-only functions rather than properties. Because of this, we are forced to treat the Global object and its methods much differently than we would in other implementations of JavaScript such as those found in browsers. As test-driven developers, this becomes an serious problem. When functions are no longer treated as objects applied to properties, it makes it difficult for us to switch the original function objects with test-doubles. This removes many of the benefits of using a dynamic language for TDD. At first I thought it was going to be impossible to use Microsoft’s JScript .NET as a desirable TDD environment, but as with most issues in JavaScript, I found a work-around.
In order to use a familiar dynamic form of the Global object in JScript .NET, I defined a special property called “Global”. This must be done after the actual .NET Global object has been completely constructed . In ASP .NET this means it must be constructed sometime after the Page_Init event has occurred. I usually put the set-up code for this property in the Page_Load event of an ASP .NET program. In We need to construct a new dynamic global object that is held in a property of the real one but contains dynamic properties that represent the real ones in the real Global object. We must then only use this property for all access to the actual Global object. We will call this property “Global”. In the object, we set up properties that hold all of the interesting functions in properties with the same name only with small first letter instead of a capital.
In .NET, this new property should also hold constructor functions for .NET objects that need to be used during processing. I add the word “new” to the front of the object name for these constructor functions as a convention.
What this will do is to make it possible to sucessfully replace any object that we need from the .NET Framework or from the Global environment with test-doubles.
Here’s an example in ASP .NET. It provides a Global property that contains Page, Server, Request, and Response. We also define four .NET elements in the example. The first is a reference to the System.IO.File object. The second and third are the constructors for System.Data.SqlClient.SqlConnection and System.Data.SqlConnection objects for data access. The fourth is the constructor for a System.IO.StreamReader.
// Test code (JSNUnit 2.7 ServerTests.js):
//
var pageReference = this;
function registerAllTests () {
newTest(“Test the existence of the Global property”).Execute = function () {
// The Work
LoadGlobalProperty();
// Test for the ASP properties
this.AreEqual(pageReference, Global.page, “Check for the Page reference”);
this.AreEqual(Server, Global.server, “Check for the Server reference”);
this.AreEqual(Request, Global.request, “Check for the Request reference”);
this.AreEqual(Response, Global.response, “Check for the Response reference”);
// File System Access
this.AreEqual(Global.file, System.IO.File,
“Check for a reference to the System.IO.File static object”);
// Data Access
this.Assert(Global.newSqlConnection()
instanceof System.Data.SqlClient.SqlConnection,
“Check for a constructor of System.Data.SqlClient.SqlConnection objects”);
this.Assert(Global.newSqlCommand() instanceof System.Data.SqlClient.SqlCommand,
“Check for a constructor of System.Data.SqlClient.SqlCommand objects”);
// Sream Reader Access
var pathToSelf = Global.server.MapPath(“ServerTests.js”);
var streamReader = Global.newStreamReader(pathToSelf);
this.Assert(Global.newStreamReader() instanceof System.IO.StreamReader,
“Check for a constructor of System.IO.StreamReader objects”);
streamReader.Close();
}
}
// Production code:
// (LoadGlobalProperty would have to be called inline on the specific
// ASP .NET Page in the Page_Load () function for instance.)
//
var Global;
function LoadGlobalProperty () {
Global = {
page: this,
server: Server,
request: Request,
response: Response,
file: System.IO.File,
newSqlConnection: function () {
return new System.Data.SqlClient.SqlConnection;
},
newSqlCommand: function () {
return new System.Data.SqlClient.SqlCommand;
},
newStreamReader: function (firstParam) {
return new System.IO.StreamReader(firstParam);
}
};
}
Now we will be able to replace .NET Framework objects with test-doubles by simply overwriting the Global property or one of its’ properties. For example, here is an object that checks to see if an event handler will redirect the user to a logon screen. It assumes that the tests and code above have already been implemented:
// Test code (JSNUnit 2.7 ServerTests.js, an addition to code above):
//
newTest(“Redirector test”).Execute = function () {
// Replace the response object on the dynamic Global property.
// This can’t be done with the real Global object.
Global.response = {
Redirect: function (url) {
this.redirectedTo = url;
}
}
// The method under test:
var lsr = Global.newLogonScreenRedirector();
lsr.goToLogonScreen();
this.AreEqual(“logon.aspx”, Global.redirectedTo,
“make sure the method redirects the user to a logon screen.”);
}
// Production code:
//
Global.newLogonScreenRedirector = {
goToLogonScreen: function () {
Global.response.Redirect(“logon.aspx”);
}
}
We have successfully stubbed out ASP .NET’s Server.Redirect function and captured the url. This flexibility demonstrates one reason why TDD in a dynamic language is so productive. To do this same thing in C# with a framework such as NUnit, might require the development of a new interface, and the implementation of a stubbed version and a production version development as well as the use of a secondary mocking framework. Actually, in C#, this line of code may not get tested because of the difficulty.
Even though JScript has this difficulty, it really is one of the most powerful .NET languages available today. It isn’t really an interpreter. JScript .NET actually compiles Just-In-Time. This is why you never have to “build” when developing with JSNUnit. It is already dynamically typed and it knows how to work quite well (even though it could probably be improved) with the .NET framework.
To try all this yourself, first learn about how to install JSNUnit for use with ASP .NET and the .NET Framework. If you are new to TDD and want a less complex example to start out with, you can also see a simple example of how to do Test-Driven Development with JavaScript.
