Hello, xUnit.js

This example is based on Examples/Source/Hello xUnit/Hello.xUnit.js and Examples/Tests/Hello xUnit/Hello.xUnitTests.js from examples.zip.

We'll start with a very naive, simplistic class, with three simple methods.

Source:

Function.RegisterNamespace("Hello"); Hello.xUnit=function(domId){ this.Alert=function(){ alert(this.GetMessage()); }; this.GetElement=function(){ return document.getElementById(domId); } this.GetMessage=function(){ return "Hello, xUnit!"; }; }

Right off, we can see that the Alert() and GetElement() methods depend on a browser host implementation, which is not available in the console hosts that we have chosen to exercise our class. The GetMessage() method has no dependencies, so we can start with that test:

Tests:

Function.RegisterNamespace("Test.Hello.xUnit"); [Import("../../Source/Hello xUnit/Hello.xUnit.js")] [Fixture] Test.Hello.xUnitTests=function(){ [Fixture] function GetMessage(){ [Fact] function ReturnsMessage(){ // Arrange var expected="Hello, xUnit!"; var target=new Hello.xUnit(); // Act var actual=target.GetMessage(); // Assert Assert.Equal(expected, actual); } } }

This is as simple (and contrived) as it gets: We declare our expectation, instantiate the class, collect the (actual) result, and compare it with our expectation.

So far, so good. Let's take a look at the Alert() method. Two things jump out at us:

  1. We need a global alert() method.
  2. The result of the GetMessage() method is passed into that alert() method.

Translating this into behavioral promises, we care that:

  1. The alert() method is called.
  2. The message it receives is the result of our own GetMessage() method.

Both of these behaviors can be verified in a single test, by verifying that the argument received in alert() matches our expectation; in other words, if we explicitly test that the correct message is passed into alert(), we implicitly know that alert() was called.

To do this, we need a global alert() method. Of course, we don't want to simply define it; that would pollute the global state for the remainder of the tests and source code that executes in our run. Instead, we'll use a Mock, which will clean up after itself:

Tests:

Function.RegisterNamespace("Test.Hello.xUnit"); [Import("../../Source/Hello xUnit/Hello.xUnit.js")] [Fixture] Test.Hello.xUnitTests=function(){ [...] [Fixture] function Alert(){ [Fact] function AlertsMessage(){ // Arrange var mockAlert=Mocks.GetMock(Object.Global(),"alert", function(targetMessage){ actual=targetMessage; } ); var target=new Hello.xUnit(); var expected=target.GetMessage(); var actual=null; // Act mockAlert(function(){ target.Alert(); }) // Assert Assert.Equal(expected,actual); } } }

We set up our mock, which overrides (in this case, by creating) the global alert method with our own function; this function accepts a targetMessage parameter, and stores the argument passed into it as our actual. We then collect our expectation from the target instance of our class, and invoke the target.Alert() method inside the delegate we pass to our mock. Finally, we compare our expectation to our result.

The final method on our class, GetElement() is even trickier; now, we need:

  1. A global document reference,
  2. With a valid getElementById() method,
  3. Which accepts an id parameter,
  4. And returns the result of that getElementById() method.

Translating into behavioral promises again, we care that:

  1. The document.getElementById() method is called,
  2. With the domId argument received by the constructor,
  3. And returns the result of the getElementById() method.

This time, we can collapse the promises into two separate tests: The method calls the dom with the expected argument; and the return value from the dom is returned by GetElement().

Tests:

Function.RegisterNamespace("Test.Hello.xUnit"); [Import("../../Source/Hello xUnit/Hello.xUnit.js")] [Fixture] Test.Hello.xUnitTests=function(){ [...] [Fixture] function GetElement(){ [Fact] function CallsGetElementByIdWithConstructorId(){ // Arrange var mockDocument=Mocks.GetMock(Object.Global(),"document",{ getElementById:function(targetId){ actual=targetId; } }); var expected="expected"; var actual=null; // Act mockDocument(function(){ new Hello.xUnit(expected).GetElement(); }); // Assert Assert.Equal(expected,actual); } [Fact] function ReturnsElementFromDom(){ // Arrange var mockDocument=Mocks.GetMock(Object.Global(),"document",{ getElementById:function(){ return expected; } }); var expected="expected"; var actual=null; // Act mockDocument(function(){ actual=new Hello.xUnit().GetElement(); }); // Assert Assert.Equal(expected,actual); } } }

Compare the behavior of the getElementById() methods in the separate mocks of each of the two tests above to understand how we fulfill the promises we set out to make. In the first case, we collect the value supplied as an argument; in the second case, we return a known value to compare against.

This is the essence of boundary control.