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?
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);
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With