12

I have some functions that read and modify files. In order to make the unit tests independent of any file system issues, I want to include the files inside the project.

However, my function should be getting the filePath, and all I can get from the assembly is a FileStream. Any idea how I can get the file path of a resource file in the project?

System.Reflection.Assembly a = System.Reflection.Assembly.Load(assemblyName);
FileStream stream = a.GetFile(assemblyName + "." + fileName);
Pang
  • 9,564
  • 146
  • 81
  • 122
Yossale
  • 14,165
  • 22
  • 82
  • 109
  • 1
    IMHO such test-data should never be built into the test and should be supplied separately (which also lets you rerun the test with different data without rebuilding). What sort of file system issues are you worried about? – Assaf Lavie Jul 20 '10 at 12:05
  • 2
    I don't like the unit tests to be depended on external resources , that can be altered , moved , changed , and so forth . This unit test is made to check 1 scenario , and I want that exact scenario to be tested , so it should be independent of the fact the file permissions can be changed , too many file handlers open , NTFS disk unavailable , and so forth ... – Yossale Jul 20 '10 at 12:07
  • I think this is a very good question. As for real unit tests I admit that using file data should be avoided but if you think of integration tests (e.g. I'm currently writing a file reader) using 'real' test data makes sense imho. – anhoppe Nov 20 '15 at 10:20

4 Answers4

8

My usual solution for this problem is that I refactor my program to open the file in the calling method and then pass a Stream instead of passing the filename and opening the file there.

For testing, this allows me to pass a MemoryStream so I can write my unit test without using the file system at all. It's sometimes even easier to check if the data has been written correctly and it's definitely faster, especially for a higher number of tests. You just have to remember to flush the MemoryStream after writing as .NET doesn't always do this automatically.

Example from one of my programs:

public TestSaveAndLoad()
{
  [... create data to save ...]
  using (MemoryStream targetStream = new MemoryStream())
  {
    target.Save(targetStream);
    targetStream.Flush();
    targetStream.Seek(0, ...);
    target.Load(targetStream);
  }
  [... assert that the loaded data equals the saved data ...]
}
Pang
  • 9,564
  • 146
  • 81
  • 122
4

An embedded resource doesn't exist on the file system, so it doesn't have any file path.

You have two options:

  • Change the API of your SUT so that it accepts a Stream instead of only a file path. This solution is much preferred.
  • Save the embedded resource to a temporary file during unit testing, making sure to delete it again after each test case.

The first solution is an excellent example of how TDD drives us towards better, more flexible APIs.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • I started with Stream s all around , but I felt like I'm juggling to much open handlers in the air. I'd give it a second thought , though. Thanks. – Yossale Jul 20 '10 at 12:56
  • 1
    You can always create overloaded methods: one that takes a file path, but really just delegates to the other that takes a Stream... – Mark Seemann Jul 20 '10 at 13:10
1

You could set the data files to be copied to the bin directory when the project is built, then reference them via Directory.GetCurrentDirectory() in your test. Or even leave them where they are and simply use a relative path based on the current directory.

Better, though, would be to restructure your code to rely the Stream class, then use a combination of mocking and dependency injection to supply a mock stream with the data -- or use a memory stream, if faking works better.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 1
    Some test runners don't have the bin directory as the current directory when running the test. Use `AppDomain.CurrentDomain.BaseDirectory` to get the deployment directory, not `Directory.GetCurrentDirectory()`. – Sjoerd Jan 21 '13 at 15:21
0

If you set the file's build action to copy, you can then predict where the file should be (probably a bunch of .. \ .. \ .. \ 's but still). I'm guessing you have an input on your method for file name, so that should work just fine.

Just as a suggestion, is it possible to abstract out the actual reading/change of that file into a method and pass a string value? The only reason why I bring that up is that it smells like an integration test (touching the file system).

jeriley
  • 1,313
  • 12
  • 20