This project is read-only.
Consider the following Test:

[TestMethod]
public void E1()
{
    ManualResetEvent mre = new ManualResetEvent(false);
    BackgroundWorker worker = new BackgroundWorker();

    int expected = 1;
    int actual = 0;
    Exception error = null;

    worker.DoWork += (o, e) => 
    {
        e.Result = 1;
    };
    worker.RunWorkerCompleted += (o, e) => 
    {
        actual = (int)e.Result;
        error = e.Error;
        mre.Set();
    };
    worker.RunWorkerAsync();
    mre.WaitOne();

    Assert.IsNull(error);
    Assert.AreEqual(expected, actual);
}

The Breakdown

ManualResetEvent mre = new ManualResetEvent(false);
The manual reset allows us to hold the test thread while our background task does it's work.

BackgroundWorker worker = new BackgroundWorker();
We will use the background worker to simulate our asynchronous task.

int expected = 1;
int actual = 0;
Exception error = null;
These values will be set by our background task and we will test these values to ensure that the test succeeds.

worker.DoWork += (o, e) => 
{
    e.Result = 1;
};
This is where the asynchronous task will perform it's real work, in this case we are returning a simple integer.

worker.RunWorkerCompleted += (o, e) => 
{
    actual = (int)e.Result;
    error = e.Error;
    mre.Set();
};
When this is exectued the test thread should be stopped at the call to mre.WaitOne(). This code snippet is run after the asynchronous work is complete and it will set the values so they can be accessed again by the test thread. Calling mre.Set() will allow the test thread to stop waiting.

worker.RunWorkerAsync();
mre.WaitOne();
Start up the background worker then Wait for a background thread to Set the mre.

Assert.IsNull(error);
Assert.AreEqual(expected, actual);
Ensure that our test values are correct!

The Problem

The problem with this code is that on silverlight you need to allow this method to complete before RunWorkerCompleted can execute since it's dispatched onto the UI thread. This is not something that is possible to do with existing frameworks. So this prompted us to build one that would allow that!

Solution

With Unit Driven you can write this test so that it runs in both .NET and Silverlight like this:

[TestMethod]
public void E2()
{
    UnitTestContext context = GetContext();
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += (o, e) =>
    {
        e.Result = 1;
    };
    worker.RunWorkerCompleted += (o, e) =>
    {
        int expected = 1;
        int actual = (int)e.Result;
        context.Assert.IsNull(e.Error);
        context.Assert.AreEqual(expected, actual);
        context.Assert.Success();
    };
    worker.RunWorkerAsync();
    context.Complete();
}
These two tests are effectively the same except the second test is the only one that will run in both environments.

The Breakdown

UnitTestContext context = GetContext();
The context behaves differently in both environments. In .NET it essentially wraps up the ManualResetEvent logic we saw in the first test so that we are compatible with NUnit and MSTest and similar testing frameworks. In Silverlight, however, the context will allow your test methods to complete so you can free up the UI thread.

worker.RunWorkerCompleted += (o, e) =>
{
    int expected = 1;
    int actual = (int)e.Result;
    context.Assert.IsNull(e.Error);
    context.Assert.AreEqual(expected, actual);
    context.Assert.Success();
};
Now we are performing our asserts directly in the RunWorkerCompleted. In Silverlight the original method has already completed so calling context.Assert.XYZ will simply use an event to communicate back with the unit testing framework while in NUnit this method will be running in a different thread so you can use the context to send exceptions (through failed asserts) back to the test thread.

You must call context.Assert.Success() to trigger the ManualResetEvent or, in Silverlight, to notify the Unit Driven framework that the test is done since the original method call is completed already.

context.Complete();
In .NET the context will call WaitOne on an internal ManualResetEvent to hold the thread while in Silverlight it will do nothing. If the context recieves a failed assert it will now throw that exception.

Last edited Jul 16, 2008 at 10:28 PM by justinc, version 2

Comments

No comments yet.