16

I am working on a project at the moment that generates a table (among other things) based on the contents of a file-system, and in turn does some meta-data modifications on the things it finds. The question is: how should tests be written around this, or set up? Is there an easy way to mock this out? Or should I setup a "sandbox"?

5 Answers 5

14

As you do always in TDD with external resources: you create one or more interfaces to your filesystem operations and "mock them out". You want to test your "table generator" and your meta-data modification code, not the file system operations itself (most probably you are using ready-made library implementations for accessing the file system).

9
  • TDD does not recommend mocking the implementation of the unit under test. See (e,g) [URL redacted -- site redirects to one that is not appropriate]
    – soru
    Apr 8, 2015 at 22:02
  • 2
    @soru: That's not what this answer recommends. It recommends first creating interfaces, then mocking the interface. So you do test the business logic, but not the filesystem interface.
    – sleske
    Apr 9, 2015 at 0:00
  • 5
    But it sounds like the business logic is defined in terms of files and directories. So the stuff that calls the file system API is the stuff that needs testing. Testing any related non-file business logic does not need mocking; just test it.
    – soru
    Apr 9, 2015 at 1:35
  • 2
    So effectively we resign from testing the actual class that interacts with file system, database, etc... instead we create a different implementation with the same interface as a mock/stub, but the actual class we leave without any kind of unit testing, because we believe that we cannot unit test it and should do integration testing to test it instead. Is this correct? Jul 13, 2016 at 6:24
  • 1
    @Ark-kun The intention of mocks is to hide these kind of file system caused bugs for the unit under test. So this is not con, it's a pro in terms of unit testing. Otherwise the unit test would test something outside of its scope (the file system integration). The answer nowhere suggests to only do unit testing of domain code and get rid of all integration tests that could be able to detect this kind of file system related bugs.
    – trixn
    Jul 18, 2018 at 12:02
13

Whats wrong with having a "test" file system?

Create a template folder/directory structure which has enough content to test you operations.

During setup of your unit test copy this initial structure (would recommend you ZIP up the template and unzip into your test area). Run your tests. Delete the whole thing during tear down.

The problem with mocking is firstly file systems, OSes and databases which belong to your project do not really qualify as external resources and secondly mocking low level system calls is both time consuming and error prone.

4
  • 5
    Mocking out file system operations will create much (!) faster running tests than using a real file system, and if this is more "error prone" is debatable, I would say it depends on the implementation. Nevertheless I think your suggestion is fine for creating automated integration tests (which I typically would do first when not doing TDD). But the OP specficially asked for TDD, and TDD unit tests have to be fast.
    – Doc Brown
    Oct 7, 2013 at 11:07
  • 1
    I think file systems, if mocked, ideally should have a whole API written and maintained by some group, because you are reinventing the wheel, if you do anything significant with the file system. Apr 8, 2015 at 21:35
  • 2
    @Doc Brown -- I am sort of assuming he wants to do dir, delete, and rename type operations all of which have edge cases which would be painful to mock. Also on modern hardware unzipping a a few small files into a directory is only a little slower than loading a Java class -- its all IO after all. Apr 9, 2015 at 7:46
  • The more I think about the test file system, the more I like it. Apr 9, 2015 at 20:37
3

I understand your question as "A good/accepted way to test a class that depends on file system operations". I donot assume that you want to test the filesystem of your os.

In order to keep the effort to 'interfaces to your filesystem operations and "mock them out"' as @Doc Brown answer suggested as small as possible it is a good idea to use java binary streams or text reader (or ther equivalent in c# or the programming language you are using) instead of using Files with filenames directly in you tdd-developed class.

Example:

Using java I have implemented a class CsvReader

public class CsvReader {
    private Reader reader;

    public CsvReader(Reader reader) {
        this.reader = reader;
    }
}

For testing I used in memory data like this

String contentOfCsv = "TestColumn1;TestColumn2\n"+
    "value1;value2\n";

CsvReader sut = new CsvReader(java.io.StringReader(contentOfCsv));

or embend testdata into the resources

CsvReader sut = new CsvReader(getClass().getResourceAsStream("/data.csv"));

In production I use the file system

CsvReader sut = new CsvReader(new BufferedReader( new FileReader( "/import/Prices.csv" ) ));

This way my CsvReader does not depend on filesystem but on an abstraction "Reader" where there is an implementation for filesystem.

3
  • 2
    The only problem here is that the OP was not speaking of file operations, but file system operations and meta data operations - I guess he meant something like listing all files in a directory, updating some EXIF information in all picture files etc.
    – Doc Brown
    Oct 7, 2013 at 16:17
  • This is correct.
    – Kirbinator
    Oct 7, 2013 at 18:37
  • 1
    You can create IDirectoryLister which has a method String[] List(String directory); then FakeDirectoryLister can implement that method by just returning new String[]{".", "..", "foo.bat", "bar.exe"}; Dec 10, 2013 at 0:23
3

This is the kind of thing you definitely need to integration test, as real-world file systems have all kind of strange behavior (like the way Windows won't allow deleting a file if any process, including the deleter, has it open).

So the TDD approach is to write the integration test first (TDD, strictly speaking, doesn't have distinct concepts of 'unit test' and 'integration test'; they are just tests). Quite likely that will be enough; so job done, stop, go home.

If not, there will be some internal complexity that isn't easy to adequately test by arranging files. In which case, you simply take that complexity out, put it in a class, and write unit tests for that class. Quite likely you will find that that common class is usable in the database, xml file, etc. cases too.

In no case would you take the fundamental core of the code you are writing and 'mock' it out in order to write tests that will pass whether or not the unit under test is wrong.

1
  • This answer really put it into perspective for me - 'unit test' and 'integration test'; they are just tests. I think realistically, this is going to be the best solution for my case - I really need to test the file system libraries I'm using for edge cases, and how the application should respond to those. If I switch to a different file system library, I don't want to have to rewrite a bunch of mocks/test code to work with the new library, but having a test folder structure and integration tests would make that much simpler.
    – tehDorf
    Nov 18, 2019 at 18:59
0

Create a wrapper for the file system operations. In the tests, pass in a mock that implements the same interface as the wrapper. In production, pass in the wrapper.

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