Awesome Go

mockfs

CategoryTesting
SubcategoryMock
Stars9

Mock filesystem for Go testing with error injection and latency simulation, built on `testing/fstest.MapFS`

About mockfs

GoDoc GoMod Size License Go codecov

mockfs

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.

Overview

mockfs enables 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

Key Features

  • Complete fs interface implementation – fs.FS, fs.ReadDirFS, fs.ReadFileFS, fs.StatFS, fs.SubFS
  • Writable filesystem – Mkdir, Remove, Rename, WriteFile with configurable modes
  • Flexible error injection – Path matching (exact, glob, regex), operation-specific or cross-operation rules
  • Error modes – Always fail, fail once, fail after N successes, or fail the next N times
  • 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.Writer functions without a full filesystem
  • Full SubFS support – Automatic path adjustment for sub-filesystems
  • Concurrency-safe – All operations safe for concurrent use

Installation

go get github.com/balinomad/go-mockfs/v2@latest

Quick Start

Basic Usage

NewMockFS, NewMockFile, NewLatencySimulator, NewLatencySimulatorPerOp, and NewFileInfo return (T, error) for invalid setup: bad path, nil MapFile, or negative duration. Check this with errors.Is(err, mockfs.ErrUsage). Each also has a Must* counterpart, for example MustNewMockFS. A Must* function panics. It does not return an error. The examples below use the Must* form. A setup mistake here is a bug in the test itself, not a condition to check err for.

package main_test

import (
    "io/fs"
    "testing"

    "github.com/balinomad/go-mockfs/v2"
)

func TestBasicFileOperations(t *testing.T) {
    // Create filesystem with initial files
    mfs := mockfs.MustNewMockFS(
        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))
    }
}

Error Injection

func TestErrorHandling(t *testing.T) {
    mfs := mockfs.MustNewMockFS(
        mockfs.File("flaky.txt", "data"),
    )

    // Simulate permission error
    if err := mfs.FailOpen("secret.txt", mockfs.ErrPermission); err != nil {
        t.Fatal(err)
    }

    // Simulate intermittent read errors (fail after 3 successes)
    if err := mfs.FailReadAfter("flaky.txt", io.EOF, 3); err != nil {
        t.Fatal(err)
    }

    // Your code under test
    err := YourFunction(mfs)
    if !errors.Is(err, mockfs.ErrPermission) {
        t.Errorf("expected permission error, got %v", err)
    }
}

Standalone File Testing

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")
    }
}

Core Concepts

Statistics: Filesystem vs File-Handle Operations

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, Close on individual open files
file, _ := mfs.Open("file.txt")
file.Read(buf)

mfs.Stats().Count(mockfs.OpOpen)  // Filesystem: 1 open

// Preferred: use OpenMockFile to obtain *MockFile directly.
mockFile, _ := mfs.OpenMockFile("file.txt")
mockFile.Stats().Count(mockfs.OpRead)  // File handle: 1 read

// Alternative: type-assert the fs.File returned by Open.
file.(*mockfs.MockFile).Stats().Count(mockfs.OpRead)

Either Stats value offers fluent assertions:

mfs.Stats().Expect().
    Count(mockfs.OpOpen, 1).
    Success(mockfs.OpRead, 5).
    NoFailures().
    Assert(t)

Error Injection

Control 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)

Write Operations

Full write support with configurable modes:

// Enable writes with overwrite mode
mfs := mockfs.MustNewMockFS(mockfs.WithOverwrite())
_ = mfs.WriteFile("output.txt", data, 0o644)

// Append mode
mfs = mockfs.MustNewMockFS(mockfs.WithAppend())
_ = mfs.WriteFile("log.txt", []byte("line1\n"), 0o644)
_ = mfs.WriteFile("log.txt", []byte("line2\n"), 0o644) // Appends

Latency Simulation

Test timeout and performance handling:

// Global latency
mfs := mockfs.MustNewMockFS(mockfs.WithLatency(100*time.Millisecond))

// Per-operation latency
mfs = mockfs.MustNewMockFS(mockfs.WithPerOperationLatency(
    map[mockfs.Operation]time.Duration{
        mockfs.OpRead:  200 * time.Millisecond,
        mockfs.OpWrite: 500 * time.Millisecond,
    },
))

Documentation

  • 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
  • Design Decisions – Rejected alternatives and rationale for contested implementation choices
  • Changelog – Release history and breaking changes

Examples

See example_*_test.go files in the repository for runnable examples:

Getting Help

License

MIT License – see LICENSE file for details.

Frequently Asked Questions

What is mockfs?

mockfs is a Testing library for the Go programming language. Mock filesystem for Go testing with error injection and latency simulation, built on `testing/fstest.MapFS`

How do I install mockfs?

Install mockfs with the Go module system using `go get balinomad/go-mockfs`. Check the repository for the current installation instructions.

What category does mockfs belong to?

mockfs is listed under Testing, specifically Mock.

← Back to Mock