Search code examples
c#unit-testingtuplesopenfiledialogfile-read

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?


Solution

  • 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);
    }