简体   繁体   中英

How can I use FileSystemWatcher from System.IO.Abstractions to monitor a mock filesystem?

I'm trying to create a class to monitor USB device arrivals and removals on Linux. On Linux, USB devices are represented as device files under /dev/bus/usb which are created/deleted in response to these events.

It seems the best way to track these events is using FileSystemWatcher . To make the class testable, I'm using System.IO.Abstractions and injecting an instance of IFileSystem to the class during construction. What I want is to create something that behaves like a FileSystemWatcher but monitors changes to the injected IFileSystem , not the real file-system directly.

Looking at FileSystemWatcherBase and FileSystemWatcherWrapper from System.IO.Abstractions , I'm not sure how to do this. At the moment I have this (which I know is wrong):

public DevMonitor(
    [NotNull] IFileSystem fileSystem,
    [NotNull] IDeviceFileParser deviceFileParser,
    [NotNull] ILogger logger,
    [NotNull] string devDirectoryPath = DefaultDevDirectoryPath)
{
    Raise.ArgumentNullException.IfIsNull(logger, nameof(logger));
    Raise.ArgumentNullException.IfIsNull(devDirectoryPath, nameof(devDirectoryPath));

    _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
    _deviceFileParser = deviceFileParser ?? throw new ArgumentNullException(nameof(deviceFileParser));
    _logger = logger.ForContext<DevMonitor>();
    _watcher = new FileSystemWatcherWrapper(devDirectoryPath);
}

In light of the fact that System.IO.Abstractions doesn't seem to support this yet, I went with this:

I defined an IWatchableFileSystem interface that extends IFileSystem :

/// <summary>
/// Represents a(n) <see cref="IFileSystem" /> that can be watched for changes.
/// </summary>
public interface IWatchableFileSystem : IFileSystem
{
    /// <summary>
    /// Creates a <c>FileSystemWatcher</c> that can be used to monitor changes to this file system.
    /// </summary>
    /// <returns>A <c>FileSystemWatcher</c>.</returns>
    FileSystemWatcherBase CreateWatcher();
}

For production purposes I implement this as WatchableFileSystem :

/// <inheritdoc />
public sealed class WatchableFileSystem : IWatchableFileSystem
{
    private readonly IFileSystem _fileSystem;

    /// <summary>
    /// Initializes a new instance of the <see cref="WatchableFileSystem" /> class.
    /// </summary>
    public WatchableFileSystem() => _fileSystem = new FileSystem();

    /// <inheritdoc />
    public DirectoryBase Directory => _fileSystem.Directory;

    /// <inheritdoc />
    public IDirectoryInfoFactory DirectoryInfo => _fileSystem.DirectoryInfo;

    /// <inheritdoc />
    public IDriveInfoFactory DriveInfo => _fileSystem.DriveInfo;

    /// <inheritdoc />
    public FileBase File => _fileSystem.File;

    /// <inheritdoc />
    public IFileInfoFactory FileInfo => _fileSystem.FileInfo;

    /// <inheritdoc />
    public PathBase Path => _fileSystem.Path;

    /// <inheritdoc />
    public FileSystemWatcherBase CreateWatcher() => new FileSystemWatcher();
}

Within my unit test class I implement it as MockWatchableFileSystem which exposes MockFileSystem and Mock<FileSystemWatcherBase> as properties which I can use for arranging & asserting within my tests:

private class MockWatchableFileSystem : IWatchableFileSystem
{
    /// <inheritdoc />
    public MockWatchableFileSystem()
    {
        Watcher = new Mock<FileSystemWatcherBase>();
        AsMock = new MockFileSystem();

        AsMock.AddDirectory("/dev/bus/usb");
        Watcher.SetupAllProperties();
    }

    public MockFileSystem AsMock { get; }

    /// <inheritdoc />
    public DirectoryBase Directory => AsMock.Directory;

    /// <inheritdoc />
    public IDirectoryInfoFactory DirectoryInfo => AsMock.DirectoryInfo;

    /// <inheritdoc />
    public IDriveInfoFactory DriveInfo => AsMock.DriveInfo;

    /// <inheritdoc />
    public FileBase File => AsMock.File;

    /// <inheritdoc />
    public IFileInfoFactory FileInfo => AsMock.FileInfo;

    /// <inheritdoc />
    public PathBase Path => AsMock.Path;

    public Mock<FileSystemWatcherBase> Watcher { get; }

    /// <inheritdoc />
    public FileSystemWatcherBase CreateWatcher() => Watcher.Object;
}

Finally, in my client class I can just do:

public DevMonitor(
    [NotNull] IWatchableFileSystem fileSystem,
    [NotNull] IDeviceFileParser deviceFileParser,
    [NotNull] ILogger logger,
    [NotNull] string devDirectoryPath = DefaultDevDirectoryPath)
{
    // ...
    _watcher = fileSystem.CreateWatcher();

    _watcher.IncludeSubdirectories = true;
    _watcher.EnableRaisingEvents = true;
    _watcher.Path = devDirectoryPath;
}

During tests, the client gets an IWatchableFileSystem that wraps MockFileSystem and returns a mock instance of FileSystemWatcherBase . During production, it gets an IWatchableFileSystem that wraps FileSystem and generates unique instances of FileSystemWatcher .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM