Course contentsShow
← C#
Lesson 1681 of 1,90815. Software Quality and TestingFree lesson

Mocking System IO Streams

Use abstraction layers to test file and network stream processing logic.

In this lesson, you will learn how to make your I/O-bound logic testable by replacing direct file and network dependencies with abstraction layers.

When your code uses FileStream or NetworkStream directly, you are tethered to the physical disk or a live network. This makes tests slow, flaky, and dependent on environment state. To solve this, we decouple the processing logic from the storage mechanism using abstractions.

The Abstraction Strategy

Instead of injecting a FileStream, define an interface that represents the stream provider. This allows you to inject a MemoryStream during testing while keeping the real FileStream for production.

public interface IStreamFactory {
    Stream CreateStream(string path, FileMode mode);
}

public class DataProcessor {
    private readonly IStreamFactory _factory;

    public DataProcessor(IStreamFactory factory) => _factory = factory;

    public void Process(string path) {
        using var stream = _factory.CreateStream(path, FileMode.Open);
        // Business logic that treats the stream as an opaque source
        using var reader = new StreamReader(stream);
        var content = reader.ReadToEnd();
        // ... process logic ...
    }
}

Mocking for Tests

Because we now depend on IStreamFactory rather than the physical filesystem, we can use a mocking framework (like Moq) or a simple manual mock to control the input.

In your test, you simply configure the mock to return a MemoryStream initialized with specific test data:

[Fact]
public void Process_ReadsCorrectData() {
    var mockFactory = new Mock<IStreamFactory>();
    var testData = new MemoryStream(Encoding.UTF8.GetBytes("Test Input"));
    
    mockFactory.Setup(f => f.CreateStream(It.IsAny<string>(), It.IsAny<FileMode>()))
               .Returns(testData);

    var processor = new DataProcessor(mockFactory.Object);
    processor.Process("dummy_path");
    
    // Assert expectations here...
}

By shifting the responsibility of "where the stream comes from" to an abstraction, your unit tests remain purely in-memory. You no longer need to clean up temporary files or worry about permissions during test execution. This pattern is equally effective for network streams, where you can mock the Stream to simulate latency or partial data packets without ever opening a socket.

Key Takeaway: By abstracting the stream creation process behind an interface, you transform hard-to-test hardware dependencies into predictable, in-memory objects that enable fast and reliable unit testing.