A javascript unit testing framework in 50 lines – plus some assertions

Surprised by a TDD session at XP-Man in which we rolled our own unit test framework from scratch - using the growing framework itself to test itself as it grew - I added some basic assertions to my 50 line javascript test runner.

I suggest that assertions should be thought of as separate to a test runner. Assertions are at the heart of - and should grow into - your domain specific testing language. So you should think of assretions as owned by your application, not the testing framework. As your application grows, so domain specific assertions should grow. A testing framework should probably provide minimal assertions for the language it runs on: the 'primitive' true/false/equal/notEqual assertions which will be used to define more domain specific assertions; and doing the legwork of tedious but common cases, such as testing equality by value of complex objects and collections.

In the case of javascript, the below might do. The tricky thing for assertions is to come up with something that is fluent and not verbose. C# extension methods are perfect for this. In javascript one could add methods to Object.prototype, but that doesn't have the same scoping restriction as C# so feels too much like trampling all over everyone else's garden: it's poor namespacing and a failure of good manners.

/* Assertions for a test framework. http://www.cafe-encounter.net/p756/ */

function Assert(actual, assertionGender){
    this.actual=actual;
    this.notted=!assertionGender;
    this.Expected=  assertionGender ? "Expected" : "Did not expect";
}

function assert(actual){return new Assert(actual, true);}
function assertNot(actual){return new Assert(actual, false);}

Assert.prototype.IsTrue = function(msg){
    if( this.actual == this.notted ){ throw msg||this.Expected + " true.";}
};

Assert.prototype.Equals = function(expected,msg){
    if( (this.actual==expected) == this.notted ){
        throw msg|| this.Expected + " " + this.actual + " to equal " + expected ;
    }
};

Assert.prototype.EqualsByPropertyValue = function(expected,msg){
    var actualValue =  JSON.stringify( this.actual );
    var expectedValue = JSON.stringify( expected );
    if( (actualValue == expectedValue) == this.notted ){
        throw msg|| this.Expected + " " + actualValue + " to equal " + expectedValue + " by value";
    }
};

$(function(){
    new Specification("SpecificationsForPrimitiveAssertionPasses",{
        isTrueShouldBeTrueForTrue : function(){
            assert(true).IsTrue();
        },
        equalsShouldPassForEqualArguments : function(){
            assert( 1+1 ).Equals( 2 );
        },
        equalsByPropertyValueShouldPassForValueEqualArguments : function(){
            assert( { a: "a", b : function(){ return "b";}} )
                .EqualsByPropertyValue( { a: "a", b : function(){ return "b";}} );
        },
        assertNotShouldReverseTheOutcomeOfAnAssertion : function(){
            assertNot(false).IsTrue();
            assertNot(1+1).Equals(3);
            assertNot({ a: "a"}).EqualsByPropertyValue( { a: "b"});
        }
    }).runTests();

    new Specification("SpecificationsForPrimitiveAssertion_Failures",{
        isTrueShouldNotBeTrueForFalse : function(){
            assert(false).IsTrue();
        },
        equalsShouldFailForNonEqualArguments : function(){
            assert(1).Equals(2);
        },
        equalsByPropertyValueShouldFailForDifferingPropertyValues : function(){
            assert( { a: "a" } ).EqualsByPropertyValue( { a: "A" } );
        },
        equalsByPropertyValueShouldFailForDifferingProperties : function(){
            assert( { a: "a" } ).EqualsByPropertyValue( { aa: "a" } );
        },
        assertNotShouldReverseTheOutcomeOfIsTrue : function(){
            assertNot(true).IsTrue();
            assertNot({ a: "a"}).EqualsByPropertyValue( { a: "a"});
        },
        assertNotShouldReverseTheOutcomeOfEquals : function(){
            assertNot(1+1).Equals(2);
        },
        assertNotShouldReverseTheOutcomeOfEqualsByValue : function(){
            assertNot({ a: "a"}).EqualsByPropertyValue( { a: "a"});
        },
        assertTrueFailureWithCustomerMessageShouldOutputCustomMessage : function(){
            assert(false).IsTrue("This assertion failure should output this custom message");
        },
        assertEqualsFailureWithCustomerMessageShouldOutputCustomMessage : function(){
            assert(1).Equals(2, "This assertion failure should output this custom message");
        },
        assertEqualsByValueFailureWithCustomerMessageShouldOutputCustomMessage : function(){
            assert({a:"a"}).EqualsByPropertyValue( 1, "This assertion failure should output this custom message");
        }

    }).runTests();
});

A javascript unit testing framework in 50 lines

Last week at xp-man we did some TDD practise - by writing a tdd framework from scratch. The surprise was, how easy it was. So here, to my surprise, a very-bare-bones javascript unit test framework in 50 lines of code including the passing unit tests for the framework itself.

/*  www.cafe-encounter.net/p734/ */

function Specification( name, tests, outputContainer ){
    this.name= name;
    this.tests= tests;
    this.outputContainer= outputContainer || $('#testResults');
}

Specification.prototype = {
    name : 'UnnamedSpecification',
    tests : {},
    outputContainer : null,

    addMyselfToUI : function(){
        this.outputContainer.append( "<li><h3>" + this.name +"</h3><ul id='" + this.name + "'></ul></li>");
    },

    testDomId:function (test) {
        return this.name + "_" + test;
    },

    addTestToUI : function addTestToUI( test ){
        $('#' + this.name).append( "<li id='" + this.testDomId(test) + "'>" + test + "<input type='checkbox'/></li>");
    },

    markTestPassed : function( test ){
        $("#" + this.testDomId(test) + " input").prop("checked",true);
    },

    markTestFailed : function( test, ex ){
        $("#" + this.testDomId(test) + " input").prop("checked",false);
        $("#" + this.testDomId(test) + " input").after("<ul>Output:<li>" + ex.toString() + "</li></ul>");
    },

    runTest : function (test) {
        this.addTestToUI(test);
        try{
            this.tests[test]();
            this.markTestPassed(test)
        } catch(ex)  {
            this.markTestFailed(test, ex);
        }
    },

    runTests : function(){
        this.addMyselfToUI();

        for (var test in this.tests) {
            if (this.tests.hasOwnProperty(test)) this.runTest( test );
        }
    }
};

$(function(){
     new Specification("TestFrameworkSpecifications", {

        aTestShouldAddItselfInTheTestResults:function() {},

        aPassingTestShouldTickTheCheckbox : function() {},

        aFailingTestShouldUntickTheCheckbox:function () {
            throw "This thrown exception should appear in the output, indicating a failure";
        }
    }).runTests();
});

And the html doc to load and run it:

<html>
<head>
    <title>Minimal Javascript Test Framework</title>
    <script type="text/javascript" src="js/jquery-1.8.2.js"></script>
    <script type="text/javascript" src="js/wsl-testframework.js"></script>
</head>
<body>
<h1>Tests</h1>
<ul id="testResults">
</ul>
</body>
</html>

As written, I admit a dependency on jQuery, but you can replace that with document.write(). The obvious omission is a bundle of pre-defined assertions, but if you are developing a domain specific testing language then assertions should be part of that package, not part of the test running framework.

The obvious oddity is that the test suite demonstrates its test failure behaviour by failing a test; and you see that that is the correct result by inspecting the output.

This has led me to think that the TDD way to learn a new language is ... to write a TDD framework for it.

Moq Mocking Indexer Properties

Mocking an indexer property with Moq is nearly but not quite straightforward. SetUpProperty() doesn't work but if you only need to moq write/read to one or two keys or you can use this code from a stackoverflow post by seanlinmt for each key:

httpContext.SetupSet(x => x.Session["key"] = It.IsAny<object>())
        .Callback((string name, object value) =>
        {
           httpContext.SetupGet(x => x.Session[name]).Returns(value);
        });

The missing functionality is that you have to setup for each specific key you are going to access. You can't do this to setup all in one:

httpContext.SetupSet(x => x.Session[ /**/ It.IsAny<string>() /**/ ] 
    = It.IsAny<object>()). ....

If you're mocking out SessionState or ApplicationState you may want to mock multiple keys even for a single test fixture, but don't want dozens of lines of setup code for each key you use.

You can at least abbreviate by shipping out the setup to an extension method so you can do this:

mockSessionStateBase.SetUpIndexerForKeys("a", "b", "c");

Like this:

public static class MoqStateDictionaryExtension
{
    private static Dictionary<Mock, Dictionary<string, object>> FakeStateDictionaries= new Dictionary<Mock, Dictionary<string, object>>();

    public static void SetUpIndexerForKeys(this Mock<HttpApplicationStateBase> @this, params string[] keys)
    {
        EnsureDictionary(@this);
        foreach (var key in keys)
        {
            string key1 = key;
            @this.SetupSet(
                    x => x[key1] = It.IsAny<object>()
                )
                .Callback<string, object>(
                    (name, val) =>
                    {
                        FakeStateDictionaries[@this][name] = val;
                        @this.SetupGet(x => x[name]).Returns(FakeStateDictionaries[@this][name]);
                    }
                );
        }
    }

    public static void SetUpIndexerForKeys(this Mock<HttpSessionStateBase> @this, params string[] keys)
    {
        EnsureDictionary(@this);
        foreach (var key in keys)
        {
            string key1 = key;
            @this.SetupSet(
                    x => x[key1] = It.IsAny<object>()
                )
                .Callback<string, object>(
                    (name, val) =>
                    {
                        FakeStateDictionaries[@this][name] = val;
                        @this.SetupGet(x => x[name]).Returns(FakeStateDictionaries[@this][name]);
                    }
                );
        }
    }

    private static void EnsureDictionary(Mock mock)
    {
        if (!FakeStateDictionaries.ContainsKey(mock)) { FakeStateDictionaries[mock] = new Dictionary<string, object>(); }
    }
    public static void RemoveDictionary(Mock mock)
    {
        if (FakeStateDictionaries.ContainsKey(mock)) { FakeStateDictionaries.Remove(mock); }
    }
    public static void RemoveDictionaries()
    {
        FakeStateDictionaries = new Dictionary<Mock, Dictionary<string, object>>();
    }
}

Used like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Moq;
using NUnit.Framework;

namespace MockingIndexersWithMoq
{
    [TestFixture]
    public class ApplicationWrapperTests
    {
        private Mock<HttpApplicationStateBase> mockApplication;
        private HttpApplicationStateBase exampleUsage;

        [SetUp]
        public void SetUp()
        {
            mockApplication = new Mock<HttpApplicationStateBase>();
            exampleUsage = mockApplication.Object;
        }

        [TearDown]
        public void ClearFakeStateDictionaries()
        {
            MoqStateDictionaryExtension.RemoveDictionary(mockApplication);
        }

        [Test]
        public void TestWhichReliesOnMockedIndexer()
        {
            mockApplication.SetUpIndexerForKeys("a", "b", "c");

            exampleUsage["a"] = 1;
            exampleUsage["a"].ShouldEqual(1);
            exampleUsage["a"] = 2;
            exampleUsage["a"].ShouldEqual(2);
            exampleUsage["b"] = 1;
            exampleUsage["b"].ShouldEqual(1);
            exampleUsage["c"].ShouldBeNull();
            exampleUsage["d"].ShouldBeNull();
            exampleUsage["e"].ShouldBeNull();
        }
    }
}