![]() |
VOOZH | about |
dotnet add package aweXpect.Testably --version 1.0.0
NuGet\Install-Package aweXpect.Testably -Version 1.0.0
<PackageReference Include="aweXpect.Testably" Version="1.0.0" />
<PackageVersion Include="aweXpect.Testably" Version="1.0.0" />Directory.Packages.props
<PackageReference Include="aweXpect.Testably" />Project file
paket add aweXpect.Testably --version 1.0.0
#r "nuget: aweXpect.Testably, 1.0.0"
#:package aweXpect.Testably@1.0.0
#addin nuget:?package=aweXpect.Testably&version=1.0.0Install as a Cake Addin
#tool nuget:?package=aweXpect.Testably&version=1.0.0Install as a Cake Tool
👁 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.
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");
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());
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()));
IFileInfo / IDirectoryInfo / IDriveInfo via .WhichHasFile, 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");
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();
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("…");
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 inGetDrives(), are not supported byHasDrive.
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() |
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
MockFileSystemnotification history. Disable it vianew MockFileSystem(o => o.WithoutNotificationHistory())only if you don't use these assertions: they throw against a history-disabled file system.
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());
ChangeDescriptionIndividual 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.
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:
null) skips that position and matches every
overload, so .File.Open() counts all Open invocations regardless of arity.recursive on Directory.Delete only matches the
two-argument overload.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).
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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
This package is not used by any NuGet packages.
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 | 135 | 6/4/2026 |
| 0.15.0 | 499 | 5/19/2026 |
| 0.14.0 | 171 | 5/18/2026 |
| 0.13.0 | 5,011 | 10/25/2025 |
| 0.12.0 | 1,814 | 9/10/2025 |
| 0.11.0 | 1,393 | 7/12/2025 |
| 0.10.0 | 748 | 6/30/2025 |
| 0.9.0 | 1,962 | 3/17/2025 |
| 0.8.0 | 1,036 | 3/2/2025 |
| 0.7.0 | 437 | 3/1/2025 |
| 0.6.0 | 203 | 2/16/2025 |
| 0.5.0 | 200 | 2/14/2025 |
| 0.5.0-pre.1 | 411 | 1/31/2025 |
| 0.4.0 | 271 | 1/30/2025 |
| 0.3.0 | 653 | 1/18/2025 |
| 0.2.0 | 1,440 | 12/13/2024 |
| 0.1.0 | 176 | 12/10/2024 |