In my previous post, I started working on a simple application that reverses the contents of a text file. I completed the first story, prompting for the input file. Next story reads:
* 2. The program should read a line from standard input which represents the full path to the file.
* - if the file does not exist, the program should return 1
* - if the file is not a text file, the program should return 2
* - if the program cannot open or read the file, it should return 3
* - the file can be empty, and the program should just output an empty file
I’ll start with the first bit, reading the file. With the confidence and experience I gained from the previous story, I can write the following test:
[Fact]
public void FileReverser_reads_file_input()
{
// Given
var inputMock = new Mock<IInput>();
inputMock.Setup(i => i.Read()).Verifiable();
var fileReverser = new App.FileReverser(null, inputMock.Object);
// When
fileReverser.ReadInput();
// Then
inputMock.Verify();
}
As previously, it doesn’t compile so I create the IInput interface with a Read method, add a private member in FileReader, update its constructor and create an empty ReadInput method. I run the tests and sure enough, the new one fails. I add a return _input.Read() statement in the method and the test is now green. I am now confident the changes are fine.
There’s two things that cross my mind now: first off, the application’s runtime doesn’t reflect this new story at all (I don’t even have a class that implements IInput). Secondly, I need to somehow verify the input I read, but I’m unsure where it will be verified. I don’t think it’s a good idea to verify it in the ReadInput method because the method will have too many different responsibilities. Maybe if I had a validator that I could test separately, I could see it clearer. I’ll do that next.
The validator should check the four cases described in the story. It should take a string representing the file path. Sounds simple enough. Let’s write a test for it. I’ll create a new test class to keep things clean.
[Fact]
public void Validator_returns_1_if_file_does_not_exist()
{
// Given
var fileName = string.Empty;
var fileChecker = new Mock<IFileChecker>();
fileChecker.Setup(fc => fc.FileExists(fileName)).Returns(false).Verifiable();
var validator = new InputFileValidator(fileChecker, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(1);
fileChecker.Verify();
}
That was harder than I thought, but I’m confident it’s not too bad. I know I need a FileChecker interface because, similarly with the Console, I don’t want to go outside of my code to test – and definitely not hit the operating system.
Because I’ve been doing big steps since the start of this post, I’ve started being somewhat scared I might break something. Whenever I realize this, I try to stop and change the TDD technique to “Fake it”. Here’s what I ended up with:
[Fact]
public void Validator_returns_1_if_file_does_not_exist()
{
// Given
var fileName = string.Empty;
var fileChecker = new Mock<IFileChecker>();
fileChecker.Setup(fc => fc.FileExists(fileName)).Returns(false).Verifiable();
var validator = new InputFileValidator(fileChecker.Object, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(1);
fileChecker.Verify();
}
//...
public class InputFileValidator
{
private readonly IFileChecker _fileChecker;
public InputFileValidator(IFileChecker fileChecker, string fileName)
{
_fileChecker = fileChecker;
}
public int Validate()
{
_fileChecker.FileExists(string.Empty);
return 1;
}
}
public interface IFileChecker
{
bool FileExists(string fileName);
}
Basically, I did the minimum amount of work to make my test pass. I’m quite certain the code isn’t right, and I’m going to write a new test to prove it. I’ll start by addressing the fact that the validator doesn’t seem to save/use the filename it gets. For this, I could write another test, but I know it will look exactly like this one, except it will have a different string as the filename. There’s an xUnit extension for that, and it has something called theories and inline data. Basically, I can write a test that takes a parameter and then xUnit runs that test multiple times, once for every InlineData attribute:
[Theory]
[InlineData("")]
[InlineData(@"some\path")]
public void Validator_returns_1_if_file_does_not_exist(string fileName)
{
// Given
var fileChecker = new Mock<IFileChecker>();
fileChecker.Setup(fc => fc.FileExists(fileName)).Returns(false).Verifiable();
var validator = new InputFileValidator(fileChecker.Object, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(1);
fileChecker.Verify();
}
This test will fail for the second inline data attribute. I run it and it does, indeed. Time to fix the validator. It’s a simple fix, and it works. Next up, the “return 1”. We need a new test:
[Fact]
public void Validator_returns_0_if_file_exists()
{
// Given
var fileName = string.Empty;
var fileChecker = new Mock<IFileChecker>();
fileChecker.Setup(fc => fc.FileExists(fileName)).Returns(true).Verifiable();
var validator = new InputFileValidator(fileChecker.Object, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(0);
fileChecker.Verify();
}
Sure enough, the test fails. However, I realize that if I change the return 1 statement to a return 0, the other test will fail – but I don’t know for sure if it fails because I changed the return statement or because it’s not getting passed the right string. I’ve done something bad: testing two different things with one test. I need to change that. I’ll remove the result.Should().Be(1) from my previous test and only keep the mock verification. That should make that test pass. And it does. I should also rename the method to better reflect that it tests.
Now, all my tests pass. This is a mistake, I’ll write a new test that checks for return 1. I won’t paste it here because it’s identical to the returns_0 one, except the mock returns false and the result is checked against 1. It looks like it’s finally time to fix the Validate method: return _fileChecker.FileExists(_fileName) ? : 0 : 1. All tests pass.
Next up, I could either go to the next validator or try to integrate my working validator with the rest of the project. I have a feeling writing the next validator will shed some light on the overall design of validators, so I’ll do that first.
The implementation is quite trivial, following the example of our previous validator, so I’ll just show you the new set of three tests that I wrote:
[Theory]
[InlineData("")]
[InlineData(@"some\path")]
public void Validator_passes_the_correct_filename_to_the_textFileChecker(string fileName)
{
// Given
var textFileCheckerMock = new Mock<ITextFileChecker>();
textFileCheckerMock.Setup(tfc => tfc.IsTextFile(fileName)).Verifiable();
var validator = new TextFileValidator(textFileCheckerMock.Object, fileName);
// When
validator.Validate();
// Then
textFileCheckerMock.Verify();
}
[Fact]
public void Validator_returns_0_if_file_is_text()
{
// Given
var fileName = string.Empty;
var textFileCheckerMock = new Mock<ITextFileChecker>();
textFileCheckerMock.Setup(tfc => tfc.IsTextFile(fileName)).Returns(true);
var validator = new TextFileValidator(textFileCheckerMock.Object, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(0);
}
[Fact]
public void Validator_returns_2_if_file_does_not_exist()
{
// Given
var fileName = string.Empty;
var textFileCheckerMock = new Mock<ITextFileChecker>();
textFileCheckerMock.Setup(tfc => tfc.IsTextFile(fileName)).Returns(false);
var validator = new TextFileValidator(textFileCheckerMock.Object, fileName);
// When
var result = validator.Validate();
// Then
result.Should().Be(2);
}
Everything passes. Time to evaluate what we’ve done. The two sets of three tests are almost identical, except a few key differences:
- they both use different interfaces for testing (IFileChecker, ITextFileChecker)
- they both use different validator classes, but the functionality is identical
- they return slightly different result codes
I think I can refactor this to clean it up a lot, but I’ll refrain from doing too much. It’s only 3 validators, and they don’t necessarily need a common base interface. The result is, we have 4 tests: one for each return value (0, 1,2 and 3) and one to make sure we actually use the interfaces, using mocks.
Next time, I’ll implement these validator interfaces, make sure the code actually runs and works and talk a bit about dependency injection. Meanwhile, you can download the code in its current state over at my github repo.