Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock Directory.GetFiles?

I am trying to figure out how or if it is possible to do the following with Moq

public class Download
{
    private IFoo ifoo;

    public Download(IFoo ifoo)
    {
        this.ifoo = ifoo;
    }

    public void Download()
    {
        var files = Directory.GetFiles("filepath"); //<<<===

        foreach (var item in files)
        {

            // do something

        }    
    }
}

In unit test.

// Arrange 

var mockFoo = new Mock<IFoo>();
mockFoo.setup( s => s.Bar()).returns(true);

var foo = new Foo(mockFoo.Object);

// Act
foo.Download()

How can I mock the files variable, so the method uses the mock version. Is this even the correct approach? As I am not mocking the class, and rather mocking the dependency how do I go about settings the files variable so it looks at mocked file string[].

like image 775
Harry Avatar asked Dec 04 '18 19:12

Harry


People also ask

How do I mock a folder?

Scoped modules (also known as scoped packages) can be mocked by creating a file in a directory structure that matches the name of the scoped module. For example, to mock a scoped module called @scope/project-name , create a file at __mocks__/@scope/project-name.

How do you mock a file system?

One option is to create a fake object that emulates the file system with an in-memory representation of it. The other possibility is to set up a test spy/mock for each test method. Considering the initial code snippet, both approaches are inappropriate. Let's see how dependency injection can help us.

What is setup in MOQ?

'Setup' mocks a method and 'Returns' specify what the mocked method should return.


1 Answers

You would need to depend on an abstraction to get your files instead of having a hard dependency on System.IO.Directory:

public interface IFileProvider
{
    string[] GetFiles(string path);
}

public class PhysicalFileProvider : IFileProvider
{
    public string[] GetFiles(string path)
    {
        return Directory.GetFiles(path);
    }
}

You would inject the abstraction in exactly the same way as you're injecting IFoo. Now you can mock IFileProvider using Moq, creating a mock that returns exactly the strings that you want it to return.

var fileProvider = new Mock<IFileProvider>();
fileProvider.Setup(x => x.GetFiles(It.IsAny<string>()))
    .Returns(new[] {"file1.txt", "file2.txt"});

You can also use Microsoft.Extensions.FileProviders.Physical which provides both the file system access and the abstraction.

public class Download
{
    private readonly IFoo _foo;
    private readonly Microsoft.Extensions.FileProviders.IFileProvider _fileProvider;

    public Download(IFoo foo, IFileProvider fileProvider)
    {
        _foo = foo;
        _fileProvider = fileProvider;
    }

    public void SomethingWithFiles()
    {
        var files = _fileProvider.GetDirectoryContents("filepath")
            .Where(item => !item.IsDirectory);

        foreach (var item in files)
        {
            // something
        }
    }
}

The concrete implementation would be PhysicalFileProvider.


One more variation. Instead of injecting an interface, inject a delegate:

public delegate string[] GetFilesFunction(string path);

public class Download
{
    private readonly IFoo _foo;
    private readonly GetFilesFunction _getFiles;

    public Download(IFoo foo, GetFilesFunction getFiles)
    {
        _foo = foo;
        _getFiles = getFiles;
    }

...

That's even easier to mock. You don't even need Moq.

var subject = new Download(mockedFoo, path => new []{"file1.txt","file2.txt"} );
like image 116
Scott Hannen Avatar answered Oct 22 '22 15:10

Scott Hannen