0

I'm constantly creating util classes like Files to be able to mock files functionality in tests.

It is convenient but violates basic idea - don't modify production code to comply with tests. I could also prepare test directory with test files but it is also sounds like not proper unit testing.

Are there good approach how to tackle this problem?

UPD

Here is example that illustrates the approach:

public class FileUtil {
    public File[] listFiles(String pathToFolder) {
        return new File(pathToFolder).listfiles();
    }
}

public class CSVImporter {
    ...
    public CSVImporter(FileUtil files) {
       ...
    }
    ...
    public void import(String pathToFolder) {
        File[] files = files.listfiles(pathToFolder);
        ...
    }
}

closed as unclear what you're asking by GlenH7, user22815, durron597, gbjbaanb, user40980 Jun 29 '15 at 0:16

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

2

Modifying code to be able to test it has nothing wrong. In fact, one of the points of TDD and unit testing in general is to encourage you to write a more maintainable code.

On the other hand, your example illustrates integration, system or functional testing, not unit testing. In unit testing, tests are as independent of the environment as possible, which, in your case, means that you are not expected to create a test directory with test files in it for the purpose of the unit test (unless your unit test is specifically focusing on a class which reads files).

You have already done the first step of introducing Dependency Injection in CSVImporter when it comes to FileUtil, and this is great. The next step is to create a stub FileUtilStub which implements IFileUtil interface, but doesn't do anything with real files: it merely returns demo data. For example, when listing files, it may return ["file1.txt", "file2.txt"], and when reading any file, it may return Hello,World.

Now come the edge cases. Since you are testing CSV stuff, what if you create a different stub FileUtilEmptyFileStub which returns an empty string when required to load a file? A FileUtilEmptyListStub may return an empty list of files. Others may return files which are very long, or files with Unicode characters, etc., to know how your class handles those cases.

A further refactoring may consist of creating a stub which takes the list of files and file contents as a parameter. This makes it possible to write less code while putting all the relevant code within the body of the unit test itself:

public void TestEmptyList()
{
    var stub = new FileUtilStub([], "hello,world");
    var importer = new CSVImporter(stub);
    // Do something with the importer and assert.
}

public void TestListWithNewlineInNames()
{
    var stub = new FileUtilStub(["file\n.txt"], "hello,world");
    var importer = new CSVImporter(stub);
    // Do something with the importer and assert.
}

...
  • Well, this looks good in theory, but IMHO you are somewhat reinventing the wheel. In Java or C# (and probably other environments), there is already something like an abstract stream class which can serve as an interface to either real files or in-memory data. So why invent an own mocking mechanism, if there is already one which can be used for testing in the framework? – Doc Brown Jun 27 '15 at 7:57
  • 1
    @DocBrown: when reading the question, I had an impression that the OP is not working at a level of a stream, but at a level of a file. If the OP can use streams directly, it would indeed be much simpler. – Arseni Mourzenko Jun 27 '15 at 10:19
1

First, there is nothing wrong in modifying your production code if it becomes more testable that way.

What you ask depends on how your production code looks like and how it deals with files. If it just opens a file, works with the file stream and closes the file again, separate the "Open/Close file" part and implement the remaining "core" part in terms of streams. That way, you can replace the file stream by a memory stream for testing purposes, and create that memory stream "on the fly" in your unit tests.

To your edit: somewhere in your CSVImporter class is a method like

  void importFile(File file){
      FileStreamReader fsr = new FileStreamReader(file);
      // do something with fsr
  }

Refactor the do something with fsr part into a function with just an InputStreamReader as a parameter.

void importFile(File file){
     FileStreamReader fsr = new FileStreamReader(file);
     importStream(fsr);
}
void importStream(InputStreamReader isr)
{
    // here is the core logic, which is useful to be tested in a unit test
}

Now make this method public or at least only package private, and test it by providing an InputStreamReader with data from memory:

 // somewhere in your unit test:      
 isr = new InputStreamReader(new ByteArrayInputStream(...)) ;
 csvImporter.importStream(isr);

(and for testing the file processing logic, do not create unit tests - create separated, automatic integration tests instead, utilizing some real files).

  • I added example, could you probably use it to show approach of stream creation and import logic separation? – Eugen Martynov Jun 26 '15 at 6:56

Not the answer you're looking for? Browse other questions tagged or ask your own question.