3

I've never used Unit tests before, but I am wanting to do so moving forward. Right now I have the simplest method I'm wanting to test:

public void GetAvailableFiles(string rootFolder)
{
   string[] dirs = Directory.GetFiles(rootFolder);
}

My unit test for this is as follows:

[Test]
public void CheckDirectory()
{     
   AvailableFile fileUpload = new AvailableFile();
   fileUpload.GetAvailableFiles("C:\\Input");      
}

When I debug this, I return all the files in my target location. However, I am struggling to come up with a valid condition in which this test can verify that it has passed.

My thinking was to have an expected count of files for this test. I have 10 files in there, so I should expect 10 results back. However, I am unsure how I could go about writing this with what I have.

How should I go about this?

N0xus
  • 2,674
  • 12
  • 65
  • 126
  • 4
    Your test setup could [create a temp directory](https://stackoverflow.com/a/278457/1269654), populate it with a bunch of empty files with known names, run your `GetAvailableFiles` on that directory, validate, and cleanup/remove the temp directory. That said, this might be a bit of a contrived scenario; essentially testing that `Directory.GetFiles` is working doesn't seem terribly useful. (Also, you don't return or do anything with the `string[] dirs` result and the method is void. You might want it to return them or expose the listing of files you got. Perhaps this is just a typo.) – Chris Sinclair Jul 20 '17 at 14:06

3 Answers3

6

It's not a unit test. It's an integration test because you interact with file system. If you want to make a unit test, you have to extract file system interaction to an interface. For example:

public interface IFileScanner {
   List<string> GetAvailableFiles(string folder);
}

[Test]
public void GetAvailableFiles_EmptyFolder_ReturnsEmptyList()
{     
   // Arrange
   IFileScanner scanner = new FileScannerEmptyFolderStub();

   // Act
   var list = scanner.GetAvailableFiles("dummy argument");

   // Assert
   Assert.IsTrue(list.Count == 0);
}

I suggest you to read some theory in book The art of unit testing with examples (Roy Osherove) before you can write unit tests.

opewix
  • 4,993
  • 1
  • 20
  • 42
  • Deleted my last comment as I thought I had it, but I'm not sure what do with the first line "FileScannerEmptyFolderStub()" – N0xus Jul 20 '17 at 14:11
  • 1
    @N0xus I think it's too complex topic to discuss here. See the book I suggest you in my answer. At the moment, you may follow `Chris Sinclair` comment to move on – opewix Jul 20 '17 at 14:12
  • 2
    I would suggest also, look into "mocking". – Dave Becker Jul 20 '17 at 14:12
  • 1
    @opewix so to test a very simple method I need to write overly complex testing methods? That seems like a weird way to go about things – N0xus Jul 20 '17 at 14:13
  • Create a test class FileScannerEmptyFolderStub which inherits from the IFileScanner. – Carra Jul 20 '17 at 14:14
  • 4
    Yes if you want to follow the idea of unit testing. But you may not need it right now. Just write tests as you can. Any tests are better than nothing – opewix Jul 20 '17 at 14:15
  • 2
    It can seem that way! but only unit test code YOU write, no point testing `Directory.GetFiles()` as it's not your code, you have to assume it works (I'd like to think MS unit tested already). – Dave Becker Jul 20 '17 at 14:23
2

The target method under test is tightly coupled to static implementation concerns (Directory).

So assuming a target SUT

public class AvailableFile {
    public void GetAvailableFiles(string rootFolder) {
       string[] files = Directory.GetFiles(rootFolder);
       //...other code using files
    }
}

Extract that out into an explicit service dependency.

public interface IDirectory {
    string[] GetFiles(string rootFolder);
}

And refactor the SUT

public class AvailableFile {
    private readonly IDirectory directory;

    public AvailableFile (IDirectory directory) {
        this.directory = directory;
    }

    public void GetAvailableFiles(string rootFolder) {
       string[] files = directory.GetFiles(rootFolder);
       //...other code using files
    }
}

An implementation of the service in production can be done like

public class DirectoryServie : IDirectory  {
    public string[] GetFiles(string rootFolder) {
        return Directory.GetFiles(rootFolder);
    }
}

Making sure that the interface and implementation are registered with your IoC container in your composition root.

With all the decoupling done you can now test your implementations that depend on these services in isolation. You really want to be testing code you control and not 3rd partly implementations, which is what Directory is. Microsoft would have tested that well enough.

Using Moq you can test you target system under test in isolation without any coupling to implementation concerns.

[Test]
public void CheckDirectory_Should_GetFiles() {
    //Arrange
    var files = new [] { 
        "fake path 1",
        "fake path 2",
        //fake path n
    };
    var rootPath = "C:\\Input";
    var service = new Mock<IDirectory>();
    service.Setup(_ => _.GetFiles(rootPath).Returns(files).Verifiable();

    var fileUpload = new AvailableFile(service.Object);

    //Act   
    fileUpload.GetAvailableFiles(rootPath);

    //Assert
    service.Verify();
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I did the exact same thing for DateTime, to be able to mock the DateTime.Now and set whatever time I need in my tests. – PmanAce Oct 19 '18 at 16:29
1

Give the method some temp directory with some files in it:

public class TestClass : IDisposable
{
    private string _directory;

    public TestClass()
    {
        _directory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(_directory);
    }

    [Fact]
    public void NoFiles()
    {
        Assert.Empty(GetAvailableFiles(_directory));
    }

    [Fact]
    public void TwoFiles()
    {
        File.WriteAllText(Path.Combine(_directory, "aaa.txt"), "");
        File.WriteAllText(Path.Combine(_directory, "bbb.txt"), "");

        var files = GetAvailableFiles(_directory);
        Assert.Equal(2, files.Length);
        Assert.Contains(Path.Combine(_directory, "aaa.txt"), files);
        Assert.Contains(Path.Combine(_directory, "bbb.txt"), files);
    }

    public void Dispose()
    {
        Directory.Delete(_directory, true);
    }

    // Method under test
    public string[] GetAvailableFiles(string rootFolder)
    {
        return Directory.GetFiles(rootFolder);
    }
}

This test is fast and doesn't require any dependencies, and so while it isn't strictly a unit test it doesn't come with many of the drawbacks normally associated with integration tests (that rely on external databases etc...)

If this method contains a significant amount of other logic (e.g. working with the contents of those files) then its a good idea to introduce a seam between that logic and filesystem access.

Justin
  • 84,773
  • 49
  • 224
  • 367