FakeTimeProvider 是 .NET 8+ 中用于测试的时间模拟工具,需安装 Microsoft.Extensions.TimeProvider.Testing 包;它不自动推进时间,必须显式调用 Advance() 或 SetUtcNow(),适用于依赖注入场景下的时间解耦测试。

C# 中没有内置的 FakeTimeProvider 类型,.NET 原生 System.TimeProvider(.NET 8+ 引入)才首次提供可替换的时间抽象,而 FakeTimeProvider 是其配套测试用实现 —— 它只在 .NET 8 及以上版本中可用,且必须显式安装 Microsoft.Extensions.TimeProvider.Testing NuGet 包。
直接用 new FakeTimeProvider() 编译失败?大概率是版本或引用缺失。下面说清楚怎么用、为什么这么配、以及哪里容易卡住。
确认是否在 .NET 8+ 项目中使用 TimeProvider
不是所有“模拟时间”需求都该用 FakeTimeProvider:它专为替代 TimeProvider.System 设计,适用于依赖注入场景下的时间解耦(比如定时任务、过期检查、重试逻辑)。如果你只是测一段代码耗时,Stopwatch 更直接。
使用前必须满足:
- .NET SDK 版本 ≥ 8.0(检查
dotnet --version) - 项目文件中已启用隐式 using 或手动引入
using System;(TimeProvider在System命名空间下) - 已安装 NuGet 包:
Microsoft.Extensions.TimeProvider.Testing(注意不是Microsoft.Extensions.DependencyInjection等基础包)
FakeTimeProvider 的核心行为和初始化方式
它不自动推进时间,所有时间“流动”必须由你显式调用 Advance() 或 SetUtcNow() 触发。这点和真实系统时间完全不同 —— 忘记调用 Advance(),后续所有 GetUtcNow() 都会返回初始值(默认是 DateTimeOffset.UtcNow 创建时刻)。
典型初始化写法:
var fake = new FakeTimeProvider(); // 此时 fake.GetUtcNow() 返回的是 fake 实例创建时的 UTC 时间 // 不是“零点”,也不是“1970-01-01”,而是构造那一刻的真实时间
如果你想从某个固定起点开始模拟(例如测试缓存过期),应主动设置:
var fake = new FakeTimeProvider(); fake.SetUtcNow(new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero));
在依赖注入中替换 TimeProvider 并验证生效
这是 FakeTimeProvider 最常见的用途:让被测类通过构造函数接收 TimeProvider,测试时传入 FakeTimeProvider 实例。
示例被测类:
public bool IsExpired(DateTimeOffset expiresAt) => _timeProvider.GetUtcNow() > expiresAt;
对应单元测试(xUnit):
[Fact]
public void IsExpired_returns_true_when_time_has_passed()
{
var fake = new FakeTimeProvider();
var service = new TokenService(fake);
<pre class="brush:php;toolbar:false;">var expiresAt = fake.GetUtcNow().AddSeconds(10);
// ⚠️ 关键:不调用 Advance,时间不会动
fake.Advance(TimeSpan.FromSeconds(15));
Assert.True(service.IsExpired(expiresAt));}
常见错误:
- 忘记在断言前调用
fake.Advance()→IsExpired始终返回false - 误以为
FakeTimeProvider会自动随测试线程休眠推进 → 它完全被动,Thread.Sleep对它无效 - 在多个测试间复用同一个
FakeTimeProvider实例但未重置 → 时间状态残留导致测试污染
与 Stopwatch 的根本区别:别混用
Stopwatch 测的是真实经过的 wall-clock 时间或 CPU 时间,适合性能分析;FakeTimeProvider 模拟的是“逻辑时间流”,用于验证时间敏感逻辑的行为,比如:
- JWT token 是否在指定时间后判定为过期
- 限流器是否在窗口重置后正确清空计数
- 后台服务是否按预期间隔触发轮询
如果你在同一个测试里既用 Stopwatch 又用 FakeTimeProvider,要明确区分目标:前者回答“这段代码跑了多久”,后者回答“在这段逻辑里,时间被当作怎样演进的”。两者底层机制无关,强行桥接只会增加理解成本。
最易被忽略的一点:FakeTimeProvider 的 Advance() 是**累积推进**,不是“跳到某时刻”。连续两次 Advance(TimeSpan.FromMinutes(1)) 相当于过了 2 分钟,而不是停在第 1 分钟那个点。需要精确跳转请用 SetUtcNow()。










