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

Comments (4)

  • Deniz Ekmek Reply

    Hey Thomas!
    Is there a way to test code like the following, because Apllication.Current.Properties returns null and therefore i’m not able to test it.
    public string RetrieveToken()
    {
    if (Application.Current.Properties.ContainsKey(“token”))
    {
    return Application.Current.Properties[“token”] as string;
    }
    return null;
    }

    March 4, 2018 at 4:56 pm
    • Thomas Claudius Huber Reply

      Hi Deniz,

      great question. Is Application.Current null or Application.Current.Properties? What ever it is: Application.Current is a dependency that you can abstract away to make your code testable. Something like this:

      interface IHaveProperties{
      IDictionary Properties{get;}
      }

      IHavePropreties _haveProperties
      constructor(IHaveProperties haveProperties)
      {
      _haveProperties = haveProperties;
      }

      public string RetrieveToken(){
      if(_haveProperties.Properties.Contains(“token”))
      {
      return _haveProperties.Properties[“token”] as string;
      }
      return null;
      }

      In a test you mock an IHaveProperties object. In production, you wrap your application in something like this:

      public class HaveProperties: IHaveProperties
      {
      public IDictionary Properties { get {return Application.Current.Properties;} }
      }

      Hope this helps,
      Thomas

      March 9, 2018 at 10:18 pm
  • Paweł Rodziewicz Reply

    Hello this post is was very helpfull for me. Thanks!. I have a question:
    Can we do it similarly for App.DialogService? Because is also always null.
    And another if you can. How automatize test with MessageBox.Show, or any dialog show?

    October 4, 2018 at 9:12 pm
    • Thomas Claudius Huber Reply

      Hi Pawel, you should create an IDialogService for dialogs, then you can have for example also an IMessageBoxService.

      Cheers,
      Thomas

      January 21, 2019 at 5:22 pm

Leave a Reply to Thomas Claudius Huber Cancel reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.