Thursday, April 21, 2005

This blog has moved. My blog can now be found at:

http://www.benmonro.com/Blog

Monday, April 18, 2005

NUnitAsp Rocks!



I've been using NUnitAsp at work for the last week or so. I must say, at first I was not too impressed because it lacked some of the features I desired, namely support for Repeaters. However, it was the only web gui unit testing for C# so I figured what the hell, I'll be a hippy and contribute some code.

So in I went hunting through the code for the DataGrid to see how it handles rows. After a while I decided, ok, time to write a unit test. I ended up writing something like this:

(sorry for the code formatting, the <pre> tag jacks up blogspot's layout)


[Test]
public void TestRepeaterRowCount
{
RepeaterTester someRepeater = new RepeaterTester("someRepeater", CurrentWebForm);
//CurrentWebForm is defined in the NUnitAspTestCase.SetUp() method.

Assert.AreEqual(3, someRepeater.ItemCount);
}

Now let me ask you this? If you know a test is going to fail, do you actually run it in the nunit-gui? I usually don't.

Of course, by now I've resharper'd my code to compile. Which brings me to one problem with Resharper, it won't create or move classes in new files. Yeah, yeah, I know, IntelliJ will.

First things first, I have to define my constructor. I figured I'd use the same signature as all the other Testers already built; 2 parameters, the aspnet control name and the container object it resides in. Easy enough:

public RepeaterTester(string aspId, Tester container) : base(aspId, container)
{
_tagXpath = "*[starts-with(@id, '" + this.HtmlId + "__ctl')]";
}



Now where the heck did that _tagXpath come from? Well, it came a few iterations later when I realized that NUnitAsp is all about XPath. See, the AspControlTester base class takes care of almost everything for you. All you need to do is define how it finds the elements on a page which it has converted into XHTML. Nifty eh?

Another thing to note is that use of the HtmlId. This is also defined in the base class, and it takes care of all the naming and containers and what not. It comes out as the Id of the control in the HTML DOM as its rendered on the page.

So now I define my Repeater.ItemCount property. For you java hippies, a property is like a built in getter and setter with nicer syntax. For you Ruby ninjas, well you do it even better than C#. :)

so here's my Repeater.ItemCount:

public int ItemCount
{
get
{
int itemCount;
try
{
HtmlTag lastChild = Tag.Children(_tagXpath + "[position() = last()]")[0];

string childId = lastChild.Attribute("id");

Match m = Regex.Match(childId, this.HtmlId + @"__ctl(\d+)");

itemCount = Convert.ToInt32(m.Groups[1].Captures[0].Value) + 1;

}
catch (HtmlTag.ElementNotVisibleException)
{
itemCount = 0;
}
return itemCount;
}
}



Again, this came after a few red/green iterations, but I finally realized that the last child in the list of Repeater controls was named with __ctlX where X is the N-1 th element in the list. Not bad, though, I wish I had ruby/perl style regular expressions.

Of course this still fails, because of that pesky call to the Tag Property. The Tag property tells the base class how to find your html element containing your control. (in this case the repeater items). Again notice the re-use of my xpath:


protected override HtmlTag Tag
{
get
{
return new HtmlTag(Browser, "(//" + _tagXpath + ")[1]/..", this.Description);
}
}



Ok, well, that's great, I can get the row count, whip-dee-friggin-doo. What if I want to get access to a control in a repeater row? Oooh, now we're talkin action.

Let's write another failing test:


[Test]
public void TestLabelInRepeaterRow()
{
LabelTester label1 = new LabelTester("Label1", repeater2.GetItem(0));

NUnit.Framework.Assert.AreEqual("Go Suns!", label1.Text);
}

That little call to repeater2.GetItem(0) is the only thing causing this to fail. Ok, no problem, resharper it and get going.


public Item GetItem(int rowNum)
{
return new Item(rowNum, this);
}



public class Item : AspControlTester
{
private int _rowNum;
private RepeaterTester _container;

public int RowNum
{
get
{
return _rowNum;
}
set
{
_rowNum = value;
}
}

public Item(int rowNum, RepeaterTester container) : base(rowNum.ToString(), container)
{
_rowNum = rowNum;
_container = container;
}




}


Run the test again and presto, we're green again. All the Item class does is serve as a container for the items inside each Repeater Item. nothing more really. Though if you wanted to add something more (like the RowNum property, its pretty straight forward.

Its really as simple as that...

And guess what, this even supports Nested Repeaters. Go ahead, try it out, write a failing test!

Wednesday, April 06, 2005

Don't say nuttin'

Its amazing to me sometimes how silly people can be. The first thing you learn in kinergarten is "if you haven't got anything nice to say, don't say anything at all." Well, I suppose its the second thing to "don't poop on the sleeping mat." But anyway, apparently some people need a rule book to know they shouldn't be posting bad things about their employers. I personally love Apollo Group, they are a fantastic company (requires free registration) to work for!