Testing Trouble with JScript and ASP .NET Page Object

Testing Trouble with JScript and ASP .NET Page Object

I’m writing this article to document a problem that I discovered while trying to do UI tests. In order to do these tests, and follow the TDD practice, I can’t write any code without a broken test. Problem code appears when you can’t find a way to test it because some behavior of the system stops you from setting up the system state without testing too much code. You may find your self either testing code that someone has already written, or testing code that you have already tested.

In Test-Driven Development it is important to be able to easily fake any interface so that it can be tested with the fewest lines of code. Dynamic languages such as JScript make this very easy except when the language suddenly doesn’t work the way you expect.

This is what the team I was working with discovered recently as we attempted to access the ASP .NET Page. We discovered that the accessors that ASP .NET usually automatically provides for all server-side controls with an ID attribute were unavailable when we tried to access the page object through an un-typed JScript property.Here’s a script that most ASP developers would know works. It displays “Hello” in a text box:

<%@ Page Language="JScript" AutoEventWireup="true" Debug="true"%>
<script language="javascript" runat="server">
    function Page_Load () {
        this.NameTextBox.Text = "Hello";
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:textbox id="NameTextBox" runat="server"/>
        </div>
   </form>
</body>
</html>

When you develop using JavaScript, you would expect the following script to work. For testing purposes, you may want to abstract your text box by one layer in order to replace it with a fake for testing with the interface. I am assuming that you are leaving the html tags the same. Here is a script that doesn’t work:

<@ Page Language="JScript" AutoEventWireup="true" Debug="true"%>
<script language="javascript" runat="server">
    function Page_Load () {
        var o = {};
        o.page = this;

        // This line causes an error:
        o.page.NameTextBox.Text = "Hello";
   }
</script>

Why doesn’t this work? I’m not really sure, but I know it doesn’t and I know that it makes it much harder for me to test. Things like this are a very unfortunate surprise when practicing TDD.

If I replace the script with this code, it does work:

<@ Page Language="JScript" AutoEventWireup="true" Debug="true"%>
    function Page_Load () {
        var o = {};
        o.page = this;

        // This line works:
        o.page.Controls[3].Controls[1].Text = "Hello";

   }
</script>

For some reason the page.Controls array can be accessed through the JScript property, but the controls on the page cannot. This is a good thing to know about, but something I don’t know how else I could have discovered it other than to just run into it in at a bad time.

I have since then discovered a way to test controls on a page in a much easier way than all of these. Assuming that you are using JSNUnit, the key is to stub out your controls in your test JavaScript file. JSNUnit comes with a ServerTests.js file that you can use.

For example, put this code into the test file:

// ServerTests.js
//
// This is a test file.  Notice that the next line defines a
// variable that is named exactly the same as the text box control
// on the page.

var NameTextBox = {Text: 'empty'}

function registerAllTests() {

    newTest("Testing the Text Box Contents").Execute = function() {
        Page_Load();
        this.AreEqual("Hello", this.NameTextBox.Text, "Says 'Hello'");
    }

}

var allTests = registerAllTests();    

In order to make this work, you will need to put this in your ServerCode.js file:

// ServerCode.js

function Page_Load () {
    this.NameTextBox.Text = "Hello";
}

What I have done here is to make a fake text box by making an interface in my test that behaves exactly as the text box is expected to. This is a common thing in dynamic languages such as JavaScript. Instead of caring about the name of an interface, it just concerns itself with the behavior of the object. I guess it assumes that if it behaves just like a particular object, then it must be one. This seems like a natural assumption to me.

To make a page that actually works you will need to have a file such as Default.aspx that looks like this:

<%@ Page Language="JScript" AutoEventWireup="true" Debug="true"%>
<script language="javascript" runat="server" src="ServerCode.js">
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:textbox id="NameTextBox" runat=server/>
        </div>
    </form>
</body>
</html>