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

Managing Global Configuration in Tests

Avoid global state pollution by scoping configurations to specific test contexts.

In this lesson, you will learn how to prevent fragile, non-deterministic test suites by isolating configuration settings instead of relying on global, static state.

The Peril of Global Configuration

When testing C# applications, it is common to need environment-specific settings (like connection strings or feature flags). A common mistake is to store these in a static class or a global configuration object shared across the entire test project.

The danger here is state pollution. If Test A modifies a static GlobalSettings.Timeout to trigger a specific edge case, and Test B runs concurrently or immediately after, Test B will inherit that modified state, leading to "flaky" tests that pass locally but fail intermittently on build servers.

Scoping via Dependency Injection

To avoid this, we must shift from global state to context-aware configuration. Instead of accessing a static singleton, inject an IConfiguration or a custom options object into your system under test (SUT) during the setup phase of each individual test.

By creating a unique configuration instance for each test context, you ensure that side effects are contained within the lifetime of that specific test.

[Fact]
public void Process_WithShortTimeout_ShouldFail()
{
    // Arrange: Create a scoped configuration instance just for this test
    var config = new Dictionary<string, string> { { "Timeout", "10ms" } };
    var configuration = new ConfigurationBuilder().AddInMemoryCollection(config).Build();
    
    // Inject the scoped instance into the SUT
    var service = new DataProcessor(configuration);

    // Act & Assert
    Assert.Throws<TimeoutException>(() => service.Process());
}

Why This Matters

When you define configuration per test, you gain several advantages:

  1. Parallel Execution: Since tests no longer mutate global objects, you can safely run your test suite in parallel without fear of cross-contamination.
  2. Deterministic Setup: Each test defines its own "universe," making it easier to reason about why a test passed or failed.
  3. Immutability: By passing configuration objects as read-only interfaces (like IOptions<T> in ASP.NET Core), you enforce that the SUT cannot accidentally alter shared settings.

Key Takeaway: Never rely on static or global configuration; instead, encapsulate your settings and inject them into each test context to guarantee isolation and parallel safety.