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

Testing with Time-Dependent Logic

Inject ISystemClock or similar interfaces to control time-sensitive business logic.

In this lesson, you will learn how to make your time-sensitive business logic deterministic and testable by abstracting the system clock.

The Problem with DateTime.Now

If your application logic relies on DateTime.Now or DateTime.UtcNow, your tests become fragile. For example, a test asserting that a "discount period has expired" might pass at 11:59 PM but fail at 12:01 AM. Tests should be deterministic—they must produce the same result every time they run, regardless of when they are executed.

Introducing ISystemClock

To solve this, we avoid calling the static DateTime properties directly. Instead, we create an abstraction to provide the current time.

First, define a simple interface:

public interface ISystemClock
{
    DateTime UtcNow { get; }
}

Then, implement a production version that wraps the actual system time:

public class SystemClock : ISystemClock
{
    public DateTime UtcNow => DateTime.UtcNow;
}

Injecting for Testability

When testing, you no longer rely on the real system clock. Instead, you inject the ISystemClock interface into your service. In your test suite, you can use a Mock or Stub to control the exact moment in time your code "sees."

Using your knowledge of mocking frameworks, you can set the clock to a fixed point in time:

// Setup: Tell the mock to always return a specific, frozen time
var mockClock = new Mock<ISystemClock>();
mockClock.Setup(c => c.UtcNow).Returns(new DateTime(2023, 10, 27, 10, 0, 0));

// Inject the mock into your service
var service = new SubscriptionService(mockClock.Object);

// Now the test is deterministic
Assert.True(service.IsExpired(subscription));

By decoupling your logic from the environment's clock, you can easily simulate edge cases—like leap years, midnight rollovers, or expired sessions—without waiting for real-world time to pass. You gain total control over the "temporal context" of your execution.

Key Takeaway: Never use DateTime.Now directly in your business logic; inject an ISystemClock abstraction so you can freeze or manipulate time during your test assertions.