Testably.Abstractions.Compression
Wraps System.IO.Compression.ZipFile and System.IO.Compression.ZipArchive as extensions on IFileSystem.
Installation
- .NET CLI
- PackageReference
- Central Package Management
dotnet add package Testably.Abstractions.Compression
<PackageReference Include="Testably.Abstractions.Compression" Version="7.2.0" />
<PackageVersion Include="Testably.Abstractions.Compression" Version="7.2.0" />
<PackageReference Include="Testably.Abstractions.Compression" />
Latest version: 7.2.0
The extensions live in the Testably.Abstractions namespace.
Why this package exists
System.IO.Compression.ZipFile operates on real string paths and System.IO.Compression.ZipArchive operates on Streams; neither knows about IFileSystem. The package bridges the two:
// With Testably.Abstractions.Compression, one line:
fileSystem.ZipFile.CreateFromDirectory("artworks", "artworks.zip");
Without the companion package (~15 lines of glue)
using MemoryStream buffer = new();
using (ZipArchive archive = new(buffer, ZipArchiveMode.Create, leaveOpen: true))
{
AddDirectory("", "artworks");
void AddDirectory(string prefix, string directory)
{
foreach (string file in fileSystem.Directory.GetFiles(directory))
{
ZipArchiveEntry entry = archive.CreateEntry(prefix + Path.GetFileName(file));
using Stream entryStream = entry.Open();
entryStream.Write(fileSystem.File.ReadAllBytes(file));
}
foreach (string sub in fileSystem.Directory.GetDirectories(directory))
{
string name = fileSystem.Path.GetFileName(sub);
archive.CreateEntry(prefix + name + "/");
AddDirectory(prefix + name + "/", sub);
}
}
}
buffer.Position = 0;
using FileSystemStream output = fileSystem.File.Create("artworks.zip");
buffer.CopyTo(output);
Extraction looks similar: open a ZipArchive in Read mode, loop the entries, recreate directories on the file system, copy each entry's stream into fileSystem.File.WriteAllBytes.
The package replaces this glue with a one-line call. Beyond brevity, it also gives you stream/path overloads on both sides, async variants on .NET 10+, and an IZipArchive / IZipArchiveEntry wrapper so the abstraction extends through the archive itself rather than stopping at the zip boundary.
On RealFileSystem, every call forwards to the matching System.IO.Compression method, so production code keeps the performance of the base class library (BCL). Only MockFileSystem uses an in-memory implementation.
ZipFile
fileSystem.ZipFile exposes the static methods of System.IO.Compression.ZipFile:
fileSystem.ZipFile
.CreateFromDirectory("artworks", "artworks.zip");
fileSystem.ZipFile
.ExtractToDirectory("artworks.zip", "vault");
using IZipArchive archive = fileSystem.ZipFile
.Open("artworks.zip", ZipArchiveMode.Update);
All overloads from the BCL are present, including the async variants on .NET 10+ (CreateFromDirectoryAsync, ExtractToDirectoryAsync, OpenAsync, OpenReadAsync).
ZipArchive
fileSystem.ZipArchive is a factory for IZipArchive from a Stream (matches the new ZipArchive(...) constructors):
using FileSystemStream stream = fileSystem.File.OpenRead("artworks.zip");
using IZipArchive archive = fileSystem.ZipArchive
.New(stream, ZipArchiveMode.Read);
foreach (IZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine(entry.FullName);
}
IZipArchive exposes Entries, Mode, CreateEntry, CreateEntryFromFile, GetEntry, ExtractToDirectory and, on .NET 10+, the async counterparts CreateEntryFromFileAsync and ExtractToDirectoryAsync. IZipArchiveEntry mirrors ZipArchiveEntry (Open, Delete, LastWriteTime, Length, CompressedLength, …).