Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test file reading method with OpenFileDialog c#

I have a function, that returns text file path and file content:

public static Tuple<string, string> OpenTextFile()
{
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog .Filter = "Text |*.txt";

    bool? accept = openFileDialog.ShowDialog();

    if (accept == true)
        return Tuple.Create(File.ReadAllText(openFileDialog.FileName, Encoding.UTF8), openFileDialog.FileName);
    else
        return null;
}

How can I unit test file reading? And is it possible to test dialog showing?

like image 718
AlIon Avatar asked Apr 09 '17 22:04

AlIon


2 Answers

That method is tightly coupled to multiple concerns. The OpenFileDialog is a UI concern and File is an IO concern. This makes testing the functionality of that method in isolation difficult but not impossible.

Extract those concerns into their own abstractions.

public interface IOpenFileDialog {
    string Filter { get; set; }
    bool? ShowDialog();
    string FileName { get; set; }
}


public interface IFileSystem {
    string ReadAllText(string path, Encoding encoding = Encoding.UTF8);
}

I would also suggest converting that static method into a service method

public interface ITextFileService {
    Tuple<string, string> OpenTextFile();
}

Its implementation would depend on the other abstractions

public class TextFileService : ITextFileService {
    readonly IOpenFileDialog openFileDialog;
    readonly IFileSystem file;

    public SUT(IOpenFileDialog openFileDialog, IFileSystem file) {
        this.openFileDialog = openFileDialog;
        this.file = file;
    }

    public Tuple<string, string> OpenTextFile() {
        openFileDialog.Filter = "Text |*.txt";

        bool? accept = openFileDialog.ShowDialog();

        if (accept.GetValueOrDefault(false))
            return Tuple.Create(file.ReadAllText(openFileDialog.FileName, Encoding.UTF8), openFileDialog.FileName);
        else
            return null;
    }
}

The implementations of the dependencies would then wrap their respective concerns.

This would also allow all the abstractions to be mocked/replaced when testing their dependents in isolation.

Here is an example of testing the method using MSTest and Moq based on the above suggestions.

[TestMethod]
public void _OpenTextFile_Should_Return_TextContext_And_FileName() {
    //Arrange
    var expectedFileContent = "Hellow World";
    var expectedFileName = "filename.txt";

    var fileSystem = new Mock<IFileSystem>();
    fileSystem.Setup(_ => _.ReadAllText(expectedFileName, It.IsAny<Encoding>()))
        .Returns(expectedFileContent)
        .Verifiable();

    var openFileDialog = new Mock<IOpenFileDialog>();
    openFileDialog.Setup(_ => _.ShowDialog()).Returns(true).Verifiable();
    openFileDialog.Setup(_ => _.FileName).Returns(expectedFileName).Verifiable();

    var sut = new TextFileService(openFileDialog.Object, fileSystem.Object);


    //Act
    var actual = sut.OpenTextFile();

    //Assert
    fileSystem.Verify();
    openFileDialog.Verify();
    Assert.AreEqual(expectedFileContent, actual.Item1);
    Assert.AreEqual(expectedFileName, actual.Item2);
}
like image 153
Nkosi Avatar answered Oct 12 '22 14:10

Nkosi


instead of directly testing the UI, which crosses a boundary, you want to abstract away the UI.

perhaps :-

interface IFileSelector
{
   string Filter {get; set'}
   bool? SelectFile()
   string FileSelected
}

then

public static Tuple<string, string> OpenTextFile(IFileSelector selector)
    {

        selector.Filter = "Text |*.txt";
        bool? accept = selector.SelectFile()
        if (accept == true)
            return Tuple.Create(File.ReadAllText(selector.FileSelected, Encoding.UTF8), selector.FileSelected);
        else
            return null;
    }

then make a UI one

public class WinformsFileSelector : IFileSelector
{
...
}

and then use a mocking framework for testing like Moq

like image 34
Keith Nicholas Avatar answered Oct 12 '22 13:10

Keith Nicholas