NQUnit: JavaScript testing within .NET / CI

QUnit is an awesome unit testing framework for JavaScript that is written by the jQuery team for unit testing jQuery.

This post describes a package I created to be able to run QUnit testing from within any .NET testing framework, and thus also from continuous integration servers.

Automated JavaScript testing is hard

The main difficulty with automated testing of JavaScript, as I alluded to in my previous post (Wow, WordPress is cool these days; I was able to create that link without opening another tab to find the URL), is that there is a reliance of having a web browser instance open to do the testing (especially if the JavaScript has any integration with the DOM). This isn’t a trivial thing to test in an automated way, although there are some things out there that go some way in addressing that issue.

Automated JavaScript testing inside your server-side testing framework is good

If you manage to achieve the difficult task of getting your JavaScript unit tests to run and return results within the context of your server-side testing framework then that is a huge win. I think it’s a win because it means you can run all of the tests for your application within the same context resulting in:

  • Better efficiency for doing your testing
  • More likelihood you will actually run your JavaScript unit tests regularly since it’s convenient to do so
  • You get continuous integration for free if you already have a CI server set up.

That last point is important – this means you get continual regression testing of your JavaScript, and if multiple people are working on the same JavaScript then you get notification as soon as a bug is introduced as a side-effect of integrating the code.

Introducing NQUnit for .NET

Joshua Flanagan did some great work in 2008 to get QUnit JavaScript tests working within NUnit and consequently within the CI server he was using. He did this by firing up a web browser session using WatiN and parsing the resultant HTML to find metadata about the tests. He then presented each test to NUnit as a separate test using the IterativeTest extension.

I tried out his code, but unfortunately it didn’t work – both NUnit and WatiN have had newer versions come out that broke his code and I couldn’t find a version of IterativeTest that worked with the latest version of NUnit. Consequently, I modified Josh’s code to create something that worked and have now released that effort as a NuGet package called NQUnit. I’ve released the code via Github with an MIT license.

Using NQUnit

The NQUnit package itself is simply a DLL that provides an NQUnit namespace. The main method you are likely to call is the static method NQUnit.GetTests, which has the following signature:

public static IEnumerable<QUnitTest> GetTests(params string[] filesToTest)

Member of NQUnit.NQUnit

Summary:
Returns an array of QUnitTest objects that encapsulate the QUnit tests within the passed in files to test.

Parameters:
filesToTest: A list of one or more files to run tests on relative to the root of the test project.

Returns:
An array of QUnitTest objects encapsulating the QUnit tests in the given files

By passing in one or more URIs to test HTML files it will fire up an instance of Internet Explorer, navigate to each of the files, run the QUnit test(s) on that page and parse the results. For each test that was run it will return an instance of a QUnitTest object in an array. This static method wraps around the QUnitParser class and provides STA threading (so you don’t need to set the whole assembly as needing STA) so WatiN works. It also will catch any exceptions that occurred while running / parsing the tests and will return a single QUnitTest object with the InitializationException parameter set if any exceptions occurred. This was necessary because when integrating with NUnit (see below) it didn’t provide any useful information when an exception was thrown from within the TestCaseSource attribute.

The QUnitTest object definition is:

    /// <summary>
    /// Encapsulates the information about a QUnit test, including the pass or fail status.
    /// </summary>
    public class QUnitTest
    {
        /// <summary>
        /// The file name the QUnit test was run from.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// The name of the test.
        /// </summary>
        public string TestName { get; set; }

        /// <summary>
        /// The result of the test ("pass" or "fail").
        /// </summary>
        public string Result { get; set; }

        /// <summary>
        /// If the test failed this contains more information explaining why.
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// Will be thrown if there was a problem initializing the QUnit test.
        /// </summary>
        public Exception InitializationException { get; set; }

        /// <summary>
        /// Provides a concise string representation of the test so that unit testing libraries can show a reasonable description of the test.
        /// </summary>
        /// <returns>A concise string representation of the test</returns>
        public override string ToString()
        {
            return string.Format("[{0}] {1}", FileName, TestName);
        }
    }

After calling the static NQUnit.GetTests method you can do what you like with the QUnitTest objects (e.g. integrate them with the testing framework of your choice).

NQUnit.NUnit

The testing framework that I use is NUnit (although I’ve been eyeing off xUnit of late – I love the way they did setup, teardown and fixture setup/teardown – really semantic). Because of this, I’ve developed a second NuGet package called NQUnit.NUnit that has code to integrate each QUnitTest object as provided from NQUnit into a separate NUnit test. The NQUnit.NUnit package has no DLLs associated with it, but will insert a heap of content files into your project, namely:

  • JavaScriptTests/NQUnit/NUnitQUnit.cs – An extension method to the QUnitTest object so you can call .ShouldPass() on the QUnitTest objects to perform an NUnit assertion. At this point the InitializationException parameter I mentioned above is rethrown if it is not null.
  • JavaScriptTests/NQUnit/QUnitTests.cs – The class with the actual NUnit tests; in order to make it really convenient to add tests it will automatically supply any .html files within the JavaScriptTests folder to NQUnit to run. The tests are provided to NUnit using the TestCaseSource attribute:
        [TestFixture]
        public class QUnitTests
        {
            [Test, TestCaseSource("GetQUnitTests")]
            public void Test(QUnitTest test)
            {
                test.ShouldPass();
            }
    
            public IEnumerable<QUnitTest> GetQUnitTests()
            {
                var testsDirectory = Path.Combine(Environment.CurrentDirectory, "JavaScriptTests");
                return global::NQUnit.NQUnit.GetTests(Directory.GetFiles(testsDirectory, "*.html"));
            }
        }
    
  • JavaScriptTests/Scripts/* – A range of JavaScript files that can be included by the .html files to do the testing; I included:
    • jQuery 1.4.4
    • jQuery Mockjax (for mocking AJAX requests; note: I made a couple of mods to make it so AJAX mocks are synchronous if you specify a response time of 0 so it can be used within specit (which doesn’t have an equivalent to asyncTest at the moment), but I’ll send a pull request to the author so the changes may become part of the core code)
    • JSON2 (to parse objects into JSON strings)
    • QUnit
    • specit (provides nice RSpec style syntax, while wrapping around QUnit; again: I made a couple of mods, this time to fix IE problems, but I’ve sent a pull request).
  • JavaScriptTests/blank.html – A sample HTML file to use to create your tests with; It’s a normal QUnit HTML file with one exception – in order to get IE to run the JavaScript from the local file system I added a Mark of the Web at the top of the file.
  • JavaScriptTests/blank.js – A sample JavaScript file to put the actual QUnit tests in.

Getting NQUnit.NUnit working

Unfortunately, after installing the NQUnit.NUnit package from NuGet you won’t be up and running straight away because you need to make two changes:

  • Change the Interop.SHDocVw DLL that was added to your project References so Embed Interop Types is set to False and Copy Local is set to true. Otherwise you will get:

    System.IO.FileNotFoundException : Could not load file or assembly ‘Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=db7cfd3acb5ad44e’ or one of its dependencies. The system cannot find the file specified.

  • Set all of the files within JavaScriptTests/Scripts and the blank.html and blank.js files to Copy Local: Copy is newer, otherwise the test won’t be able to find the files. I might work on a powershell script to get that automatically happening when you install in the NuGet package, but in the meantime you will need to do that manually.

Compatibility

I should note that the ReSharper test runner is not smart enough to separate out the different tests so it will only return error information for the last test that failed. The NUnit and Test-driven.NET test runners understand the tests. I have tested this on TeamCity successfully; you do need to ensure that TeamCity can interact with the desktop to be able to open an IE instance though.

Integrating with a web project

Your QUnit tests will likely be testing files in a separate (web) project within your solution. In order to get the tests to run against the latest version of the files without having to manually copy them you can add a post-build event to the test project, e.g.:

copy “$(ProjectDir)..SampleWebAppScripts” “$(TargetDir)JavaScriptTestsScripts”

Where SampleWebApp is the name of your web project. To see this working in practice clone my github repository and check it out. That also shows an example of using the SpecIt syntax for the tests.

Any questions / problems?

Feel free to leave a comment here or raise an issue on the Git hub project page.

11 Replies to “NQUnit: JavaScript testing within .NET / CI”

  1. vs2010. resharper test runner. I cant’ get it to work.
    With one test, in one external javascript file. I get this error.

    Assert.AreEqual failed. Expected:. Actual:. Could not run test due to error: ‘System.IndexOutOfRangeException: Index was outside the bounds of the array.
    at WatiN.Core.UtilityClasses.LazyList`1.get_Item(Int32 index)
    at WatiN.Core.UtilityClasses.LazyList`1.System.Collections.Generic.IList.get_Item(Int32 index)
    at WatiN.Core.BaseComponentCollection`2.get_Item(Int32 index)
    at NQUnit.QUnitParser.d__f.MoveNext() in C:UsersRobDocumentsVisual Studio 2010ProjectsQUnitValidationNQUnitQUnitParser.cs:line 63
    at System.Linq.Enumerable.d__14`2.MoveNext()
    at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
    at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
    at NQUnit.NQUnit.c__DisplayClass2.b__1() in C:UsersRobDocumentsVisual Studio 2010ProjectsQUnitValidationNQUnitNQUnit.cs:line 50’ (in ”)

    … …


    QUnit example

  2. Rob, I think I found the cause…

    I had mistakenly deleted these markup tags

    -h1 id=”qunit-header”
    -h2 id=”qunit-banner”
    -h2 id=”qunit-userAgent”
    -ol id=”qunit-tests”

    This was a pretty good runner. I easily got it working with the Resharper5 testrunner by writing this single test

    [TestClass]
    public class JavascriptTests
    {
    [TestMethod]
    public void RunAll()
    {
    foreach (var test in GetQUnitTests())
    {
    if(test.InitializationException == null)
    Assert.AreEqual(“pass”,test.Result,true, string.Format(“Test ‘{2}’ failed with message ‘{0}’ (in ‘{1}’) “, test.Message, test.FileName, test.TestName));
    else
    Assert.AreEqual(“pass”, test.Result, true, string.Format(“Could not run test due to error: ‘{0}’ (in ‘{1}’) “, test.InitializationException.ToString(), test.FileName));
    }
    }

    public IEnumerable GetQUnitTests()
    {
    var testsDirectory = Environment.CurrentDirectory;
    return
    global::NQUnit.NQUnit.GetTests(Directory.GetFiles(testsDirectory, “*.htm”, SearchOption.AllDirectories));
    }

  3. Too quick on the trigger there. Guess I have to file an issue after all. Still getting the IndexOutOfBounds exception…. Tests run fine in the browser. Seems the testrunner method is failing instantly when the IE9 process is starting

    1. Sudeep,

      The library uses WatiN at the moment, so testing with Firefox is a possibility, but the code would need to be modified to do that. Another alternative (also would need a code modification), would be to use Selenium and then there is a much wider range of browsers that could be used for the testing.

      I personally don’t see a huge amount of value in changing it from just IE:

      • We (and I assume most web developers) use Firefox or Chrome while writing the unit tests and then having it in IE is a fairly good cross-browser check (if something weird is going to happen with the JS it’s likely to be in IE).
      • It would be more useful for developers (I think) to put effort into getting cross-browser testing working at a user interface level rather than a unit testing level because then you are doing proper integration testing, which has more value (assuming that you still have the unit testing occurring in at least one browser as well to give a degree in confidence at that low level).
    1. Hi Jeff,

      It certainly will.

      You simply need to reference the NQUnit package rather than NQUnit.NUnit and then create something similar to what I did to make the assertions with NUnit.

      What I ended up doing for NUnit was implementing the following helper:

          public static class NUnitNQUnitHelpers
          {
              public static void ShouldPass(this QUnitTest theTest)
              {
                  if (theTest.InitializationException != null)
                      throw new Exception("The QUnit initialization failed.", theTest.InitializationException);
      
                  Assert.That(theTest.Result, Is.EqualTo("pass"), "Test: " + theTest.TestName + Environment.NewLine + theTest.Message);
              }
          }
      

      And then my NUnit test is:

          [TestFixture]
          public class QUnitTests
          {
              [Test, TestCaseSource("GetQUnitTests")]
              public void Test(QUnitTest test)
              {
                  test.ShouldPass();
              }
      
              public IEnumerable GetQUnitTests()
              {
                  var testsDirectory = Path.Combine(Environment.CurrentDirectory, "JavaScriptTests");
                  return global::NQUnit.NQUnit.GetTests(Directory.GetFiles(testsDirectory, "*.html"));
              }
          }
      

      You can do something similar with any test framework. MSTest might not support the TestCaseSource stuff (which is nice because it means each JavaScript test is treated as a separate unit test in NUnit), but that’s fine, you can simply have a loop within your test that performs the assertions for each JavaScript test.

  4. I’m having an issue getting this to work. The call to NQUnit.GetTests returns an empty collection of tests even though the call to GetFiles does in fact return a list of valid html files.

    Do you have any suggestions?

    return global::NQUnit.NQUnit.GetTests(Directory.GetFiles(testsDirectory, "*.html"));

    1. That usually means there is some problem running the tests. After you build the solution you should navigate to the files in the bin directory (or wherever it is they were copied) and then run them in IE directly to ensure they work. Usually they won’t work when the MOTW marker isn’t in the file.

Leave a Reply

Your email address will not be published. Required fields are marked *