How to Test Code that Uses Singletons like Application.Current.Dispatcher?

Got a question this morning from a student of my course WPF and MVVM: Test Driven Development.

Hey Thomas, how to test code that is using Application.Current.Dispatcher? Application.Current is null when the test is executed, which leads to an exception.

If you’re using async/await in your ViewModel, you might have less need to use the Dispatcher at all, as async/await marshalls back to the Thread on which you’ve created the ViewModel, which is usually the UI-Thread. But anyway, let’s look at an example that shows the problem with the Dispatcher.

Let’s assume you have this simple ViewModel:

public class ViewModel
{
  public async Task LoadAsync()
  {
    await Application.Current.Dispatcher.InvokeAsync(
      () => Result = "This is the result");
  }

  public string Result { get; private set; }
}

The LoadAsync-method uses the Dispatcher via the static Application.Current-property. The callback passed to the InvokeAsync-method does nothing more than setting the Result-property of the ViewModel. That might not be as complex as your real code, but it is sufficient to show the problem with unit testing here.

Now let’s assume you’ve written this little test below. You call the LoadAsync-method of the ViewModel and you expect that the Result-property has the value “This is the result”.

[Fact]
public async Task ShouldLoadResult()
{
  var expectedResult = "This is the result";

  var viewModel = new ViewModel();
  await viewModel.LoadAsync();

  Assert.Equal(expectedResult, viewModel.Result);
}

When you run the Unit Test above, it blows up, as Application.Current is null. Now remember what you know about TDD, it’s like with everything else in TDD: Application.Current.Dispatcher is a dependency that you need to move out of your class to make it testable. So, just define an interface with stuff you need. For the ViewModel used here that interface looks like this:

public interface IDispatcherWrapper
{
  Task InvokeAsync(Action callback);
}

The next thing is to implement a class for your production code. (Ok, theoretically we should write the test first. But let’s be pragmatic and let’s just change the code, so that it’s testable). The snippet below shows an implementation for your production code:

public class DispatcherWrapper : IDispatcherWrapper
{
  private Dispatcher _dispatcher;

  public DispatcherWrapper(Dispatcher dispatcher)
  {
    if (dispatcher == null)
    {
      throw new ArgumentNullException(nameof(dispatcher));
    }
    _dispatcher = dispatcher;
  }
  public Task InvokeAsync(Action callback)
  {
    return _dispatcher.InvokeAsync(callback).Task;
  }
}

Now you can adjust your ViewModel accordingly to use that new IDispatcherWrapper-interface:

public class ViewModel
  {
    private IDispatcherWrapper _dispatcherWrapper;

    public ViewModel(IDispatcherWrapper dispatcherWrapper = null)
    {
      // At runtime, your DI-container might not pass in anything
      // So you create a new DispatcherWrapper in that case
      _dispatcherWrapper = dispatcherWrapper ?? new DispatcherWrapper(Application.Current.Dispatcher);
    }
    public async Task LoadAsync()
    {
      await _dispatcherWrapper.InvokeAsync(
        () => Result = "This is the result");
    }

    public string Result { get; private set; }
  }

Look at the constructor in the snippet above: If the passed in IDispatcherWrapper is null, it creates a new DispatcherWrapper with the Application.Current.Dispatcher. It stores the IDispatcherWrapper in a _dispatcherWrapper-field and uses it in the LoadAsync-method. Now in a unit test, you can just pass in any IDispatcherWrapper-implementation you like. You could use a mocking-library to set up an IDispatcherWrapper, or you can do it manually like in the snippet below.

public class FakeDispatcherWrapper : IDispatcherWrapper
{
  public Task InvokeAsync(Action callback)
  {
    callback();
    return Task.Delay(1);
  }
}

With the FakeDispatcherWrapper in place, the adjustment in your Unit Test is minor. All you need to adjust in the Unit Test is to pass in the FakeDispatcherWrapper to the ViewModel’s constructor:

[Fact]
public async Task ShouldLoadResult()
{
  var expectedResult = "This is the result";

  var viewModel = new ViewModel(new FakeDispatcherWrapper());
  await viewModel.LoadAsync();

  Assert.Equal(expectedResult, viewModel.Result);
}

Run the test, it’s green and you got the expected result.

Hope this helps!

Thanks,
Thomas

Share this post

Leave a Reply

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

*