Rasmus Hansen
Rasmus Hansen

Reputation: 1573

How to unit test dialog interaction

I have the following class:

public class DirectoryFinder : IDirectoryFinder
{
    public string GetDirectory(string whereTo)
    {
        FolderBrowserDialog dialog = new FolderBrowserDialog {Description = whereTo};
        DialogResult result = dialog.ShowDialog();
        return result == DialogResult.OK ? dialog.SelectedPath : string.Empty;
    }
}

How would I go about verifying that it returns the correct data? eg. string.Empty or whatever was selected in the dialog depending on what the user click?

I'm using NUnit as testing framework.

Upvotes: 6

Views: 8761

Answers (2)

Alan
Alan

Reputation: 326

I would like to thank Matthew Strawbrige for his excellent answer. I started out with his answer but I found out that because I was using constructor injection (Caliburn Micro) I customized it to suit myself. First the interface. (I've added a set to the SelectedPath so I can set the starting directory).

public interface IFolderBrowserDialogWrapper
{
    string SelectedPath { get; set; }
    string Description { get; set; }
    DialogResult ShowDialog();
}

Then the implementation

public class FolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
{
    private FolderBrowserDialog _dialog;

    public FolderBrowserDialogWrapper()
    {
        _dialog = new FolderBrowserDialog();
    }

    public DialogResult ShowDialog()
    {
        return _dialog.ShowDialog();
    }

    public string SelectedPath
    {
        get { return _dialog.SelectedPath;  }
        set { _dialog.SelectedPath = value; }
    }

    public string Description
    {
        get { return _dialog.Description; }
        set { _dialog.Description = value; }
    }
}

The constructor injection which will use the normal FolderBrowseDialog from the class above under normal conditions which you will have specified in your injection framework ;-)

public class TestBrowserDialog : Caliburn.Micro.Screen
{
    private IFolderBrowserDialogWrapper _folderBrowserDialogWrapper;

    public TestBrowserDialog(IFolderBrowserDialogWrapper folderBrowserDialogWrapper)
    {
        _folderBrowserDialogWrapper = folderBrowserDialogWrapper;
    }

    ...
    public void Browse()
    {
    ...
    }
    ...
}

And the command executed. I'm using Caliburn Micro which calls the method directly. In other frameworks, it could a delegate command or something else. My Browse method is linked to a button on the View, so clicking the button on the view, activates the Browse command and opens up a BrowseFolderDialog as wanted.

    public void Browse()
    {
        _folderBrowserDialogWrapper.Description = "Your description goes here";
        DialogResult result = _folderBrowserDialogWrapper.ShowDialog();
        string path = result == DialogResult.OK ? _folderBrowserDialogWrapper.SelectedPath : string.Empty;

        // Process your result.
        SourceDirectory = path;
    }

A fragment of my test code. Here I can test the behaviour of the Browse command was as expected without bringing up the BrowseFolderDialog during the unit test!

    [Test]
    public void TestBrowserDialog_Browse_SetsSourceDirectory()
    {
        // Arrange - This is using NSubstitute for Mocking, NUnit for testing
        IFolderBrowserDialogWrapper _folderBrowserDialogWrapper = Substitute.For<IFolderBrowserDialogWrapper>();
        _folderBrowserDialogWrapper.ShowDialog().Returns(DialogResult.OK);
        string testFileDirectory = Path.Combine(NUnit.Framework.TestContext.CurrentContext.TestDirectory, "Directory 1");
        _folderBrowserDialogWrapper.SelectedPath.Returns(testFileDirectory);

        // Insert your own _folderBrowserDialogWrapper for testing purposes
        TestBrowserDialog sut = new TestBrowserDialog(_folderBrowserDialogWrapper);

        // Action
        sut.Browse();

        // Assert - I'm using FluentAssertions
        sut.SourceDirectory.Should().Be(testFileDirectory);
    }

I hope this gives someone else ideas on what to do. Matthew Strawbridge's answer has certainly helped me.

Upvotes: 3

Matthew Strawbridge
Matthew Strawbridge

Reputation: 20610

One option is to separate out the untestable UI part from the testable business logic:

public string GetDirectory(string whereTo)
{
    FolderBrowserDialog dialog = new FolderBrowserDialog { Description = whereTo };
    DialogResult result = dialog.ShowDialog();

    return GetDirectory(dialog.SelectedPath, result);
}

public string GetDirectory(string selectedPath, DialogResult result)
{
    return result == DialogResult.OK ? selectedPath : string.Empty;
}

So you would just test the second method, which becomes easy.


Another option would be to use mocking/faking of the UI components. However, FolderBrowserDialog is sealed, which makes this harder.

You could do something like this, but it's probably overkill.

First, define an interface for just the parts you want to use:

public interface IFolderBrowserDialogWrapper
{
    DialogResult ShowDialog();
    string SelectedPath { get; }
}

Then wrap the real FolderBrowserDialog in your new interface:

public class FolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
{
    private readonly FolderBrowserDialog m_dialog;

    public DialogResult ShowDialog()
    {
        return m_dialog.ShowDialog();
    }

    public string SelectedPath
    {
        get { return m_dialog.SelectedPath; }
    }

    public FolderBrowserDialogWrapper(FolderBrowserDialog dialog)
    {
        m_dialog = dialog;
    }
}

And create a fake version for testing, which just returns the values passed into its constructor:

public class FakeFolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
{
    private readonly DialogResult m_result;
    private readonly string m_selectedPath;

    public DialogResult ShowDialog()
    {
        return m_result;
    }

    public string SelectedPath
    {
        get { return m_selectedPath; }
    }

    public FakeFolderBrowserDialogWrapper(string selectedPath, DialogResult result)
    {
        m_selectedPath = selectedPath;
        m_result = result;
    }
}

Then your method can use a FolderBrowserDialogWrapper for a real dialog:

public string GetDirectory(string whereTo)
{
    var f = new FolderBrowserDialogWrapper(
        new FolderBrowserDialog { Description = whereTo });
    return GetDirectory(f);
}

public string GetDirectory(IFolderBrowserDialogWrapper dialog)
{
    DialogResult result = dialog.ShowDialog();
    return result == DialogResult.OK ? dialog.SelectedPath : string.Empty;
}

And tests can use a FakeFolderBrowserDialogWrapper to bypass the UI:

[Test]
public static void TestDirectoryFinderGetDirectoryWithOKExpectThePath()
{
    const string expectedPath = @"C:\temp";

    var dlg = new FakeFolderBrowserDialogWrapper(expectedPath, DialogResult.OK);

    var df = new DirectoryFinder();
    string result = df.GetDirectory(dlg);

    Assert.That(result, Is.EqualTo(expectedPath));
}

[Test]
public static void TestDirectoryFinderGetDirectoryWithCancelExpectEmptyString()
{
    const string expectedPath = @"C:\temp";

    var dlg = new FakeFolderBrowserDialogWrapper(expectedPath, DialogResult.Cancel);

    var df = new DirectoryFinder();
    string result = df.GetDirectory(dlg);

    Assert.That(result, Is.EqualTo(string.Empty));
}

But that's probably over-the-top unless you're creating lots of FolderBrowserDialogs elsewhere in your code as well.

Upvotes: 12

Related Questions