5

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?

Nkosi
  • 191,971
  • 29
  • 311
  • 378
Alexandr Smushko
  • 317
  • 4
  • 21

4 Answers4

6

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);
}
Nkosi
  • 191,971
  • 29
  • 311
  • 378
2

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

Keith Nicholas
  • 41,161
  • 15
  • 82
  • 145
1

Yes, you can create a test file to read from in your test project. I generally put stuff like this in a folder called Assets.

Then instead of an OpenFileDialog, you just specify the exact path to the test file's location and pass that into your function and validate it with asserts as per the usual.

However I don't think this function needs a Unit Test. It's pretty explicit in what it does.

I feel like what you do with the tuple this function returns is what you should be testing, in which case your Unit test should just create the tuple by hand, and do your logic with that.

IE your test would probably start with:

TestFunction() {
    var TestString = "My Test data";
    var testTuple = new Tuple.Create(TestString, "Name");
    Assert.That(MyTupleLogic(testTuple), Is.Whatever());
}

You don't really need to test that Microsoft's Tuple.Create and OpenFileDialog work, which is what the test you propose would be checking.

I only bother running unit tests on functions that have logic I entered

1

Your method has two concerns: selecting a file in UI and reading it's content.

  • So at first you better split it in SelectTextFile and ReadAllText.
  • Then see that the second has actually no logic defined by you but just one .NET call. Not sure whether you'll have more value if you test it.
  • At second you have two boundaries here: UI and the File System, which are not under your control. If you have a lot of time and want to cover nearly 100% of code with unit tests, then, like already mentioned in other answers you abstract them away behind some interface methods: IFileDialog.SelectTextFile and IFileSystem.ReadAllText
  • Use your mocking library of choice, e.g. Moq, or direct implemented values to imitate some test cases
python_kaa
  • 910
  • 11
  • 22