1.10.2014

C# async/await makes reactive testing expressive!

Isolating asynchronous behavior isn't always possible, especially when it comes to creating integration tests. However, with the new async/await features in c# 5, testing reactive interfaces is a breeze and can be very expressive.

FileSystemWatcher and events

FileSystemWatcher is commonly used to monitor for changes to a directory. It's been around since .Net 1.1. Whenever I work with event based interfaces, I prefer to wrap them with an Observable. Testing this often requires an integration test. Here's some code to convert the FileSystemWatcher.Changed event to an observable:

IObservable<FileSystemEventArgs> Changed = Observable
    .FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => Watcher.Changed += h, h => Watcher.Changed -= h)
    .Select(x => x.EventArgs);

I'm using this to create an ObservableFileSystemWatcher adapter. I'll be referring to this in the following tests.

The following code is captured in this gist, look at the revision history to see the changes between each of the following samples.

Designing a test

Historically, testing asynchronous behavior required blocking calls and hackery to capture information for our assertions. Here's how we might be tempted to start testing the observable wrapper:

[Test]
public void WriteToFile_StreamsChanged()
{
    using (var watcher = new ObservableFileSystemWatcher(c => { c.Path = TempPath; }))
    {
        FileSystemEventArgs changed = null;
        watcher.Changed.Subscribe(c =>
        {
            changed = c;
        });
        watcher.Start();

        File.WriteAllText(Path.Combine(TempPath, "Changed.Txt"), "foo");

        Expect(changed.ChangeType, Is.EqualTo(WatcherChangeTypes.Changed));
        Expect(changed.Name, Is.EqualTo("Changed.Txt"));
    }
}

To test the Changed observable, we Subscribe to Changed and then capture the last result into a local changed variable. This way we can run assertions on the changed notification. Next, we Start the watcher, create a new file, and verify we get a notification.

However, when we run this test, it fails. The file notification is asynchronous and thus non-deterministic. We don't know if it will happen before or after our assertions are executed.

Waiting for the result

Historically, we'd have to modify this test to wait for the changed notification. We could use a ManualResetEvent to wait:

[Test]
[Timeout(2000)]
public void WriteToFile_StreamsChanged()
{
    using (var watcher = new ObservableFileSystemWatcher(c => { c.Path = TempPath; }))
    {
        var reset = new ManualResetEvent(false);
        FileSystemEventArgs changed = null;
        watcher.Changed.Subscribe(c =>
        {
            changed = c;
            reset.Set();
        });
        watcher.Start();

        File.WriteAllText(Path.Combine(TempPath, "Changed.Txt"), "foo");

        reset.WaitOne();
        Expect(changed.ChangeType, Is.EqualTo(WatcherChangeTypes.Changed));
        Expect(changed.Name, Is.EqualTo("Changed.Txt"));
    }
}

The reset will block the test at the call to WaitOne just before our assertions. When the changed notification happens, Set will be called in our subscriber and the test will complete. To be safe, the test has also been modified to Timeout after 2 seconds.

Now our test works, but it's not pretty :(

Making it expressive with async/await

Thanks to the new c# 5 async/await language features, we can fix this. Here's the new way we can write this test:

[Test]
[Timeout(2000)]
public async Task WriteToFile_StreamsChanged()
{
    using (var watcher = new ObservableFileSystemWatcher(c => { c.Path = TempPath; }))
    {
        var firstChanged = watcher.Changed.FirstAsync().ToTask();
        watcher.Start();

        File.WriteAllText(Path.Combine(TempPath, "Changed.Txt"), "foo");

        var changed = await firstChanged;
        Expect(changed.ChangeType, Is.EqualTo(WatcherChangeTypes.Changed));
        Expect(changed.Name, Is.EqualTo("Changed.Txt"));
    }
}

Observables are awaitable, which means we can wait (non-blocking) for the next item. In this case, we can use FirstAsync().ToTask() to create a Task, that upon completion, will contain the first result of the observable sequence. This task is named firstChanged above. This task could complete at any time. Of course in this case, nothing will happen until we create the test file. We're free to continue setting up the test as we've already indicated the desire to capture the first result!

Next, we Start the watcher, create the test file and then the magic, we use await to wait for the result of our firstChanged task. Once the task is complete, the changed notification will be captured into our changed variable. I love how readable this is 'changed equals wait for the first changed'! Once the first changed notification arrives, the assertions will run and the test will complete.

Strive for Expressive Tests

The historical strategy works, but the new style is more compact and doesn't require so much brain power to understand. This new style makes this test much easier to write and much easier to maintain. Try this out and see how it improves your own testing, let me know what you come up with!





comments powered by Disqus