开发者

C# (Unit Testing): I need to make it impossible to write to a file through code

Basically, here's the situation. I have the following:

public IService Service { get; set; } //Set to MyMockedService class.

public Boolean DoFoo()
{
    //possible other ways of returni开发者_JAVA百科ng true/false...

    Boolean success = true;

    //Get FileInfo[] items

    foreach (var item in items)
        DoOtherFoo(item);
}

public Boolean DoOtherFoo(FileInfo fileInfo)
{
    String filepath = //manipulate fileInfo.FullName;

    Byte[] file = Service.GetFile(filepath)
    try
    {
        WriteBinaryFile(filepath, file); //How can I force file writing to throw an exception
    }
    catch (Exception)
    {
        return false;
    }
}

Basically, In testing DoFoo() I have a lot of paths that could return true/false. I've unit tested all of them except the final one... where it attempts to write the files and if even one of the files can't be written for some reason, it fails and sends back false. At first I figured if I tried to setup a bad filename such as "bad*file" it would throw an exception in the WriteFile but I didn't even get that far because I can't create a FileInfo object using illegal characters. So I'm looking for another way to make it so that it would be impossible to write the file so I can get back a false.


Good unit tests are isolated ones. It means they are absolutely idependent of any environment (files, database, network etc.). If your code uses files to store data in it, you should hide by some interface, the production and test code would use different implmementation. Production will really do Write to file, test one will only emulate it.

public interface IStorage
{
  bool StoreToFile(string path, string file, byte[] data);
}

public class Storage : IStorage
{
  public bool StoreToFile( ... )
  {
     return WriteToFile( ... );
  }
}

public class StorageMock : IStorage
{
   public bool StoreToFile (...)
   {
      return false;  //or true, depends on you test case
   }
}

Now in tests you are able to "subtitue" real implmementaion with fake one. This is called mocking.

Desing that takes into considiration called "Inversion of Control". There are also a bunch of frameworks that allows you to use "Investion of Control" throught dependency injection (StructureMap, Ninject, Wisdor)


Is WriteBinaryFile a method in this same class?

You could create an object with this sole responsibility, but start with an abstraction:

public interface IBinaryFileWriter
{
    void WriteBinaryFile(string filepath, Byte[] file);
}

Now you can inject this dependency into the class that you are testing, preferably as a constructor argument.

In your application, you'll use an implementation of IBinaryFileWriter that does exactly what your current method does.

In tests, though, you can supply a mock that you can configure to throw an exception.


Your unit test can use System.IO.File.Open(...) with FileShare set to None. Any other process will fail to open that file.


It depends on the internals of WriteBinaryFile. You need to be testing WriteBinaryFile at a more granular level. I assume that that's your function...I don't recognize that off the top of my head.


Here is the decompiled code for ctor of FileInfo

public FileInfo(string fileName)
{
    if (fileName == null)
    {
        throw new ArgumentNullException("fileName");
    }
    base.OriginalPath = fileName;
    string fullPathInternal = Path.GetFullPathInternal(fileName);
    new FileIOPermission(FileIOPermissionAccess.Read, new string[] { fullPathInternal }, false, false).Demand();
    this._name = Path.GetFileName(fileName);
    base.FullPath = fullPathInternal;
}

So after creating a FileInfo you can set the FullPath and OriginalPath to an invalid filepath by reflection and make the WriteBinary throwing exceptoin

Just like that:

 FileInfo info = new FileInfo("c:\\1.txt");
 info.GetType().BaseType.GetField(
      "FullPath", 
      BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance)
   .SetValue(info, "invalidpath");
 info.GetType().BaseType.GetField(
      "OriginalPath", 
       BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance)
   .SetValue(info, "invalidpath");
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜