A flexible and feature-rich mock filesystem for Go testing, built on testing/fstest.MapFS with comprehensive error injection, latency simulation, and write operation support.
mockfs enables robust testing of filesystem-dependent code by providing a complete in-memory filesystem with precise control over behavior, errors, and performance characteristics. It implements Go's standard fs interfaces and adds powerful testing capabilities designed for both experienced Go developers and those new to filesystem testing.
Built for testing scenarios that require:
- Simulating I/O failures and edge cases
- Testing timeout and retry logic
- Verifying filesystem access patterns
- Testing concurrent filesystem operations
- Validating write operations and transactions
- Complete
fsinterface implementation –fs.FS,fs.ReadDirFS,fs.ReadFileFS,fs.StatFS,fs.SubFS - Writable filesystem –
Mkdir,Remove,Rename,WriteFilewith configurable modes - Flexible error injection – Path matching (exact, glob, regex), operation-specific or cross-operation rules
- Error modes – Always fail, fail once, or fail after N successes
- Latency simulation – Global, per-operation, serialized, or async with independent file-handle state
- Dual statistics tracking – Separate counters for filesystem-level vs file-handle operations
- Standalone file mocking – Test
io.Reader/io.Writerfunctions without a full filesystem - Full
SubFSsupport – Automatic path adjustment for sub-filesystems - Concurrency-safe – All operations safe for concurrent use
go get github.com/balinomad/go-mockfs/v2@latestpackage main_test
import (
"io/fs"
"testing"
"github.com/balinomad/go-mockfs/v2"
)
func TestBasicFileOperations(t *testing.T) {
// Create filesystem with initial files
mfs := mockfs.NewMockFS(
mockfs.File("config.json", `{"setting": "value"}`),
mockfs.Dir("data",
mockfs.File("input.txt", "test data"),
),
)
// Read file
data, err := fs.ReadFile(mfs, "config.json")
if err != nil {
t.Fatal(err)
}
// List directory
entries, err := mfs.ReadDir("data")
if err != nil {
t.Fatal(err)
}
// Check statistics
stats := mfs.Stats()
if stats.Count(mockfs.OpOpen) != 1 {
t.Errorf("expected 1 open, got %d", stats.Count(mockfs.OpOpen))
}
}func TestErrorHandling(t *testing.T) {
mfs := mockfs.NewMockFS(
mockfs.File("flaky.txt", "data"),
)
// Simulate permission error
mfs.FailOpen("secret.txt", mockfs.ErrPermission)
// Simulate intermittent read errors (fail after 3 successes)
mfs.FailReadAfter("flaky.txt", io.EOF, 3)
// Your code under test
err := YourFunction(mfs)
if !errors.Is(err, mockfs.ErrPermission) {
t.Errorf("expected permission error, got %v", err)
}
}func TestFileReader(t *testing.T) {
// Create file without filesystem
file := mockfs.NewMockFileFromString("test.txt", "content")
// Test function that accepts io.Reader
result := YourReaderFunction(file)
// Verify statistics
stats := file.Stats()
if stats.BytesRead() == 0 {
t.Error("expected bytes to be read")
}
}mockfs tracks operations at two levels for precise verification:
- Filesystem operations (
(*MockFS).Stats()):Open,Stat,ReadDir,Mkdir,Remove, etc. - File-handle operations (
(*MockFile).Stats()):Read,Write,Closeon individual open files
file, _ := mfs.Open("file.txt")
file.Read(buf)
mfs.Stats().Count(mockfs.OpOpen) // Filesystem: 1 open
// Assert to concrete pointer to access file-handle stats
file.(*mockfs.MockFile).Stats().Count(mockfs.OpRead) // File handle: 1 readControl errors with fine-grained rules:
// Simple: always fail specific operations
mfs.FailOpen("file.txt", mockfs.ErrPermission)
// Pattern matching: fail all .log files
mfs.ErrorInjector().AddGlob(mockfs.OpRead, "*.log", io.EOF, mockfs.ErrorModeAlways, 0)
// Conditional: fail after N successes
mfs.FailReadAfter("data.bin", io.EOF, 5)Full write support with configurable modes:
// Enable writes with overwrite mode
mfs := mockfs.NewMockFS(mockfs.WithOverwrite())
mfs.WriteFile("output.txt", data, 0o644)
// Append mode
mfs = mockfs.NewMockFS(mockfs.WithAppend())
mfs.WriteFile("log.txt", []byte("line1\n"), 0o644)
mfs.WriteFile("log.txt", []byte("line2\n"), 0o644) // AppendsTest timeout and performance handling:
// Global latency
mfs := mockfs.NewMockFS(mockfs.WithLatency(100*time.Millisecond))
// Per-operation latency
mfs = mockfs.NewMockFS(mockfs.WithPerOperationLatency(
map[mockfs.Operation]time.Duration{
mockfs.OpRead: 200 * time.Millisecond,
mockfs.OpWrite: 500 * time.Millisecond,
},
))- API Reference – Complete API documentation on pkg.go.dev
- Usage Guide – Advanced patterns, best practices, and real-world examples
- Migration Guide – Upgrading from v1 to v2
See example_*_test.go files in the repository for runnable examples:
- Review test files (
*_test.go) for comprehensive usage examples - Check GoDoc for detailed API documentation
- Read the Usage Guide for patterns and best practices
- File issues at github.com/balinomad/go-mockfs/issues
MIT License – see LICENSE file for details.