aweXpect.Testably 1.0.0

Prefix Reserved
dotnet add package aweXpect.Testably --version 1.0.0
                    
NuGet\Install-Package aweXpect.Testably -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="aweXpect.Testably" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="aweXpect.Testably" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="aweXpect.Testably" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add aweXpect.Testably --version 1.0.0
                    
#r "nuget: aweXpect.Testably, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package aweXpect.Testably@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=aweXpect.Testably&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=aweXpect.Testably&version=1.0.0
                    
Install as a Cake Tool

aweXpect.Testably

Changelog

Nuget Coverage Mutation testing badge

Drop-in aweXpect expectations for the file-system and time-system mocks from Testably.Abstractions: MockFileSystem, IFileInfo, IDirectoryInfo, IDriveInfo, IFileVersionInfo, IFileSystemWatcher, IFileSystemStatistics and ITimerMock.

File system (IFileSystem)

Verify that a file, directory or drive is present in the file system. Every positive assertion has a DoesNot… counterpart:

IFileSystem fileSystem = new MockFileSystem();
fileSystem.Directory.CreateDirectory("my/path");
fileSystem.File.WriteAllText("my-file.txt", "some content");

await That(fileSystem).HasDirectory("my/path");
await That(fileSystem).HasFile("my-file.txt");

await That(fileSystem).DoesNotHaveDirectory("not/here");
await That(fileSystem).DoesNotHaveFile("missing.txt");

File chain

HasFile(path) returns a result that lets you chain assertions about the file's content and timestamps without re-resolving it:

IFileSystem fileSystem = new MockFileSystem();
fileSystem.File.WriteAllText("my-file.txt", "some content");

await That(fileSystem).HasFile("my-file.txt").WithContent("some content").IgnoringCase();
await That(fileSystem).HasFile("my-file.txt").WithContent().NotEqualTo("some unexpected content");
await That(fileSystem).HasFile("my-file.txt").WithContent(new byte[] { 0x73, 0x6F, 0x6D, 0x65 });

You can compare against another file on the same file system:

fileSystem.File.WriteAllText("my-other-file.txt", "SOME CONTENT");
fileSystem.File.WriteAllText("my-third-file.txt", "some other content");

await That(fileSystem).HasFile("my-file.txt").WithContent().SameAs("my-other-file.txt").IgnoringCase();
await That(fileSystem).HasFile("my-file.txt").WithContent().NotSameAs("my-third-file.txt");

…and against the file's timestamps. .Within(tolerance) widens the comparison to a window:

await That(fileSystem).HasFile("my-file.txt").WithCreationTime(DateTime.Now).Within(1.Second());
await That(fileSystem).HasFile("my-file.txt").WithLastAccessTime(DateTime.Now).Within(1.Second());
await That(fileSystem).HasFile("my-file.txt").WithLastWriteTime(DateTime.Now).Within(1.Second());

Directory chain

HasDirectory(path) exposes sub-collections:

IFileSystem fileSystem = new MockFileSystem();
fileSystem.Directory.CreateDirectory("foo/bar1");
fileSystem.Directory.CreateDirectory("foo/bar2/baz");
fileSystem.File.WriteAllText("foo/bar/my-file.txt", "some content");

await That(fileSystem).HasDirectory("foo").WithDirectories(d => d.HasCount().EqualTo(2));
await That(fileSystem).HasDirectory("foo/bar").WithFiles(f => f
    .All().ComplyWith(x => x.HasContent("SOME CONTENT").IgnoringCase()));

Bridging to IFileInfo / IDirectoryInfo / IDriveInfo via .Which

HasFile, HasDirectory and HasDrive each expose a .Which property that returns the resolved IFileInfo / IDirectoryInfo / IDriveInfo so the subject-level assertions below light up directly in the chain:

await That(fileSystem).HasFile("my-file.txt").Which.HasLength(12).And.HasContent("some content");
await That(fileSystem).HasDirectory("logs").Which.IsEmpty();
await That(fileSystem).HasDrive("D:\\").Which.IsReady().And.HasDriveFormat("NTFS");

File (IFileInfo)

IFileInfo fileInfo = fileSystem.FileInfo.New("my-file.txt");

await That(fileInfo).Exists();
await That(fileInfo).DoesNotExist();

await That(fileInfo).HasName("my-file.txt");
await That(fileInfo).HasExtension(".txt");
await That(fileInfo).HasLength(12);
await That(fileInfo).HasContent("some content");
await That(fileInfo).HasContent(new byte[] { 0x73, 0x6F, 0x6D, 0x65 });

await That(fileInfo).IsReadOnly();
await That(fileInfo).IsNotReadOnly();

await That(fileInfo).HasAttribute(FileAttributes.ReadOnly);
await That(fileInfo).DoesNotHaveAttribute(FileAttributes.Hidden);

await That(fileInfo).HasCreationTime(DateTime.Now).Within(1.Second());
await That(fileInfo).HasLastAccessTime(DateTime.Now).Within(1.Second());
await That(fileInfo).HasLastWriteTime(DateTime.Now).Within(1.Second());

HasAttribute / DoesNotHaveAttribute use flag containment, so FileAttributes.ReadOnly | FileAttributes.Hidden satisfies HasAttribute(FileAttributes.ReadOnly). The empty / default value is rejected with an ArgumentException to avoid silent passes.

On .NET 10 or later, WhoseParent switches the subject to the containing directory so the directory-level assertions can be reused:

await That(fileInfo).WhoseParent.HasName("docs").And.IsNotEmpty();

Directory (IDirectoryInfo)

IDirectoryInfo dirInfo = fileSystem.DirectoryInfo.New("foo");

await That(dirInfo).Exists();
await That(dirInfo).DoesNotExist();

await That(dirInfo).HasName("foo");

await That(dirInfo).IsEmpty();
await That(dirInfo).IsNotEmpty();

await That(dirInfo).HasFile("bar/my-file.txt");
await That(dirInfo).DoesNotHaveFile("bar/missing.txt");
await That(dirInfo).HasDirectory("bar").Which.HasFile("my-file.txt");
await That(dirInfo).DoesNotHaveDirectory("not-here");

await That(dirInfo).HasAttribute(FileAttributes.Directory);
await That(dirInfo).DoesNotHaveAttribute(FileAttributes.Hidden);

await That(dirInfo).HasCreationTime(DateTime.Now).Within(1.Second());
await That(dirInfo).HasLastAccessTime(DateTime.Now).Within(1.Second());
await That(dirInfo).HasLastWriteTime(DateTime.Now).Within(1.Second());

HasFile / HasDirectory on IDirectoryInfo return the same chain results as the file-system-level versions, so .WithContent(...), .WithLastWriteTime(...), .Which, etc. all work as well: the path is resolved relative to the directory.

On .NET 10 or later, WhoseParent switches to the parent directory:

await That(dirInfo).WhoseParent.HasName("…");

Drive (IDriveInfo)

MockFileSystem fileSystem = new(o => o.SimulatingOperatingSystem(SimulationMode.Windows));
fileSystem.WithDrive("D:", d => d.SetTotalSize(2048));

IDriveInfo driveInfo = fileSystem.DriveInfo.New("D:");

await That(driveInfo).HasAvailableFreeSpace(2048);
await That(driveInfo).HasTotalSize(2048).And.HasTotalFreeSpace(2048);
await That(driveInfo).HasDriveFormat("NTFS");
await That(driveInfo).HasDriveType(DriveType.Fixed);
await That(driveInfo).HasName(driveInfo.Name).And.HasVolumeLabel(driveInfo.VolumeLabel);
await That(driveInfo).IsReady();

Drives are matched by name (case-insensitive) against IFileSystem.DriveInfo.GetDrives(). UNC drives, which do not appear in GetDrives(), are not supported by HasDrive.

File version info (IFileVersionInfo)

IFileVersionInfo instances obtained via MockFileSystem.FileVersionInfo.GetVersionInfo can be asserted directly. The values come from MockFileSystem.WithFileVersionInfo(glob, builder):

MockFileSystem fileSystem = new();
fileSystem.WithFileVersionInfo("*.dll", v => v
    .SetCompanyName("Acme")
    .SetProductName("Anvil")
    .SetFileVersion("1.2.3.4")
    .SetIsDebug(true));
fileSystem.File.WriteAllText("Acme.dll", "");

IFileVersionInfo info = fileSystem.FileVersionInfo.GetVersionInfo("Acme.dll");

await That(info).HasCompanyName("Acme").And.HasProductName("Anvil");
await That(info).HasFileVersion("1.2.3.4").And.HasFileMajorPart(1);
await That(info).IsDebug().And.IsNotPreRelease();

Dedicated assertions cover every IFileVersionInfo property — strings via Has…(string), the integer version parts via Has…(int) and the booleans as Is…() / IsNot…() pairs.

Property Assertion
Comments HasComments(string)
CompanyName HasCompanyName(string)
FileDescription HasFileDescription(string)
FileName HasFileName(string)
FileVersion HasFileVersion(string)
InternalName HasInternalName(string)
Language HasLanguage(string)
LegalCopyright HasLegalCopyright(string)
LegalTrademarks HasLegalTrademarks(string)
OriginalFilename HasOriginalFilename(string)
PrivateBuild HasPrivateBuild(string)
ProductName HasProductName(string)
ProductVersion HasProductVersion(string)
SpecialBuild HasSpecialBuild(string)
FileBuildPart HasFileBuildPart(int)
FileMajorPart HasFileMajorPart(int)
FileMinorPart HasFileMinorPart(int)
FilePrivatePart HasFilePrivatePart(int)
ProductBuildPart HasProductBuildPart(int)
ProductMajorPart HasProductMajorPart(int)
ProductMinorPart HasProductMinorPart(int)
ProductPrivatePart HasProductPrivatePart(int)
IsDebug IsDebug() / IsNotDebug()
IsPatched IsPatched() / IsNotPatched()
IsPreRelease IsPreRelease() / IsNotPreRelease()
IsPrivateBuild IsPrivateBuild() / IsNotPrivateBuild()
IsSpecialBuild IsSpecialBuild() / IsNotSpecialBuild()

File-system notifications

A MockFileSystem raises notifications when files or directories change. Run the code under test, then assert against the notifications it produced:

MockFileSystem fileSystem = new();
fileSystem.File.WriteAllText("my-file.txt", "some content");

await That(fileSystem).TriggeredNotification();
await That(fileSystem).TriggeredNotification(c => c.Name == "my-file.txt");

.Within(timeout) (default 30 s) lets the assertion wait for asynchronous notifications. If a matching notification already fired, the assertion completes synchronously; otherwise it waits up to the timeout for a late arrival:

_ = Task.Run(() => fileSystem.File.WriteAllText("foo.txt", "x"));
await That(fileSystem).TriggeredNotification().Within(100.Milliseconds());

DidNotTriggerNotification mirrors the same shapes and short-circuits as soon as a matching notification is observed:

await That(fileSystem).DidNotTriggerNotification().Within(100.Milliseconds());
await That(fileSystem).DidNotTriggerNotification(c => c.Name == "secret.txt");

Both accept a Quantifier (AtLeast, AtMost, Exactly, Between, Never, Once) so you can assert how often the notification fires, and a .Which(c => …) callback that composes the per-notification expectations from ChangeDescription:

fileSystem.File.WriteAllText("a.txt", "x");
fileSystem.File.WriteAllText("b.txt", "y");

await That(fileSystem).TriggeredNotification(c => c.ChangeType == WatcherChangeTypes.Created)
    .Exactly(2.Times());

await That(fileSystem)
    .TriggeredNotification()
    .Which(c => c.HasName("a.txt").And.HasChangeType(WatcherChangeTypes.Created))
    .Exactly(1.Times());

Replay of historical notifications relies on the MockFileSystem notification history. Disable it via new MockFileSystem(o => o.WithoutNotificationHistory()) only if you don't use these assertions: they throw against a history-disabled file system.

Watcher events (IFileSystemWatcher)

An individual IFileSystemWatcher can also be the subject. The watcher must come from a MockFileSystem, and EnableRaisingEvents must be true for any event to be observed. Only events fired on this specific watcher count; events fired on other watchers of the same MockFileSystem are ignored.

MockFileSystem fileSystem = new();
using IFileSystemWatcher watcher = fileSystem.FileSystemWatcher.New("/");
watcher.EnableRaisingEvents = true;
fileSystem.File.WriteAllText("my-file.txt", "some content");

await That(watcher).Triggered();
await That(watcher).Triggered(c => c.Name == "my-file.txt");

await That(watcher).DidNotTrigger().Within(100.Milliseconds());
await That(watcher).DidNotTrigger(c => c.Name == "secret.txt");

Triggered and DidNotTrigger share the same shape as the notification assertions: a Quantifier (AtLeast, AtMost, Exactly, Between, Never, Once), a .Within(timeout) (default 30 s), and a .Which(c => …) callback that composes the per-change expectations from ChangeDescription:

await That(watcher)
    .Triggered()
    .Which(c => c.HasName("my-file.txt").And.HasChangeType(WatcherChangeTypes.Created))
    .Exactly(1.Times());

ChangeDescription

Individual ChangeDescription instances can be asserted directly:

await That(change).HasChangeType(WatcherChangeTypes.Created);
await That(change).DoesNotHaveChangeType(WatcherChangeTypes.Deleted);

await That(change).HasFileSystemType(FileSystemTypes.File);
await That(change).HasNotifyFilters(NotifyFilters.LastWrite);

await That(change).HasName("my-file.txt").And.HasPath("/abs/my-file.txt");
await That(renamedChange).HasOldName("old.txt").And.HasOldPath("/abs/old.txt");

HasChangeType, HasFileSystemType and HasNotifyFilters use flag containment (so a LastWrite | FileName change satisfies HasNotifyFilters(NotifyFilters.LastWrite)); the empty / default value is rejected with an ArgumentException to avoid silent passes.

Recorded calls (IFileSystemStatistics)

MockFileSystem.Statistics records every method call and property access on the mock. .Recorded() exposes a fluent mirror over these recordings, so you can assert what the system under test actually called:

MockFileSystem fileSystem = new();
fileSystem.File.WriteAllText("foo.txt", "x");

await That(fileSystem.Statistics).Recorded().File.WriteAllText().Once();
await That(fileSystem.Statistics).Recorded().File.WriteAllText(path: p => p == "foo.txt").Once();

The mirror has one entry per IFileSystem member (.File, .Directory, .FileInfo[path], .DirectoryInfo[path], .DriveInfo, .FileStream, .FileSystemWatcher, .FileVersionInfo, .Path), with one method per underlying API and an indexer ([path]) for per-instance buckets. Every result inherits the count vocabulary (Once, Twice, Never, Exactly, AtLeast, AtMost, Between, …).

Property reads and writes are recorded with .Get() / .Set():

fileSystem.FileInfo.New("foo.txt").IsReadOnly = true;

await That(fileSystem.Statistics).Recorded().FileInfo["foo.txt"].IsReadOnly.Set().Once();
await That(fileSystem.Statistics).Recorded().DirectoryInfo["foo"].Exists.Get().AtLeast().Once();

Each parameter on a mirror method is an optional Func<T, bool> predicate matched positionally against the recorded argument:

  • Supplying no predicate (or null) skips that position and matches every overload, so .File.Open() counts all Open invocations regardless of arity.
  • A predicate whose position exceeds an overload's arity excludes that overload, so filtering recursive on Directory.Delete only matches the two-argument overload.
  • A predicate whose type differs from the recorded type at that position silently excludes that overload, so filtering searchOption on Directory.EnumerateDirectories never matches the EnumerationOptions overload.

A handful of methods can't be filtered fully through this positional model because two overloads place different types at the same recording position (File.Open / FileInfo.Open with FileStreamOptions, FileSystemWatcher.WaitForChanged with TimeSpan).

Timer (ITimerMock)

A MockTimeSystem exposes timers as ITimerMock. You can assert how often the timer callback was executed without blocking the test thread:

MockTimeSystem timeSystem = new();
ITimerMock timer = (ITimerMock)timeSystem.Timer.New(
    _ => { }, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(10));

await That(timer).Executed(3.Times()).Within(5.Seconds());

Executed() accepts a Quantifier (AtLeast, AtMost, Exactly, Between, Never, Once) and exposes .Within(timeout) for asynchronous execution. The assertion polls ITimerMock.ExecutionCount until the quantifier is satisfied or the timeout expires (30 seconds by default).

await That(timer).Executed().AtLeast(2.Times()).Within(100.Milliseconds());
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on aweXpect.Testably:

Repository Stars
TestableIO/System.IO.Abstractions
Just like System.Web.Abstractions, but for System.IO. Yay for testable IO access!
Version Downloads Last Updated
1.0.0 39 6/4/2026
0.15.0 385 5/19/2026
0.14.0 169 5/18/2026
0.13.0 5,009 10/25/2025
0.12.0 1,812 9/10/2025
0.11.0 1,393 7/12/2025
0.10.0 747 6/30/2025
0.9.0 1,960 3/17/2025
0.8.0 1,033 3/2/2025
0.7.0 433 3/1/2025
0.6.0 202 2/16/2025
0.5.0 200 2/14/2025
0.5.0-pre.1 410 1/31/2025
0.4.0 271 1/30/2025
0.3.0 653 1/18/2025
0.2.0 1,439 12/13/2024
0.1.0 176 12/10/2024