|
| 1 | +using Bing.DependencyInjection; |
| 2 | +using Microsoft.Extensions.DependencyInjection; |
| 3 | + |
| 4 | +namespace Bing.Tests.DependencyInjection; |
| 5 | + |
| 6 | +/// <summary> |
| 7 | +/// 依赖注入 测试 |
| 8 | +/// </summary> |
| 9 | +public class DependencyInjectionTest |
| 10 | +{ |
| 11 | + /// <summary> |
| 12 | + /// 测试 - 单例(Singleton)解析瞬时(Transient)服务时,不应依赖于当前作用域(Scope) |
| 13 | + /// </summary> |
| 14 | + [Fact] |
| 15 | + public void Singletons_Should_Resolve_Transients_Independent_From_Current_Scope() |
| 16 | + { |
| 17 | + //Arrange |
| 18 | + |
| 19 | + var services = new ServiceCollection(); |
| 20 | + |
| 21 | + services |
| 22 | + .AddSingleton<MySingletonServiceUsesTransients>() |
| 23 | + .AddTransient<MyTransientServiceUsesSingleton>() |
| 24 | + .AddTransient<MyTransientService>(); |
| 25 | + |
| 26 | + MySingletonServiceUsesTransients singletonService; |
| 27 | + |
| 28 | + using (var serviceProvider = services.BuildServiceProvider()) |
| 29 | + { |
| 30 | + // Act: 创建多个作用域,并验证 Transient 服务的独立性 |
| 31 | + using (var scope = serviceProvider.CreateScope()) |
| 32 | + { |
| 33 | + scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
| 34 | + scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
| 35 | + } |
| 36 | + |
| 37 | + using (var scope = serviceProvider.CreateScope()) |
| 38 | + { |
| 39 | + scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
| 40 | + scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesSingleton>().DoIt(); |
| 41 | + scope.ServiceProvider.GetRequiredService<MySingletonServiceUsesTransients>().ShouldNotBeDisposed(); |
| 42 | + } |
| 43 | + |
| 44 | + singletonService = serviceProvider.GetRequiredService<MySingletonServiceUsesTransients>(); |
| 45 | + singletonService.ShouldNotBeDisposed(); |
| 46 | + } |
| 47 | + |
| 48 | + // Assert: 确保 Transient 实例在主服务释放时被正确释放 |
| 49 | + singletonService.ShouldBeDisposed(); |
| 50 | + } |
| 51 | + |
| 52 | + /// <summary> |
| 53 | + /// 测试 - 当主服务被释放时,依赖的瞬时(Transient)服务是否被正确释放。 |
| 54 | + /// </summary> |
| 55 | + [Fact] |
| 56 | + public void Should_Release_Resolved_Services_When_Main_Service_Is_Disposed() |
| 57 | + { |
| 58 | + var services = new ServiceCollection(); |
| 59 | + |
| 60 | + services |
| 61 | + .AddTransient<MyTransientServiceUsesTransients>() |
| 62 | + .AddTransient<MyTransientService>(); |
| 63 | + |
| 64 | + using (var serviceProvider = services.BuildServiceProvider()) |
| 65 | + { |
| 66 | + MyTransientServiceUsesTransients myTransientServiceUsesTransients; |
| 67 | + |
| 68 | + using (var scope = serviceProvider.CreateScope()) |
| 69 | + { |
| 70 | + myTransientServiceUsesTransients = scope.ServiceProvider.GetRequiredService<MyTransientServiceUsesTransients>(); |
| 71 | + |
| 72 | + myTransientServiceUsesTransients.DoIt(); |
| 73 | + myTransientServiceUsesTransients.DoIt(); |
| 74 | + |
| 75 | + myTransientServiceUsesTransients.ShouldNotBeDisposed(); |
| 76 | + } |
| 77 | + |
| 78 | + // Assert: 确保在作用域被释放后,Transient 实例被释放 |
| 79 | + myTransientServiceUsesTransients.ShouldBeDisposed(); |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + /// <summary> |
| 84 | + /// 测试 - 内层作用域(Inner Scope)应解析新的 Scoped 服务实例。 |
| 85 | + /// </summary> |
| 86 | + [Fact] |
| 87 | + public void Inner_Scope_Should_Resolve_New_Scoped_Service() |
| 88 | + { |
| 89 | + var services = new ServiceCollection(); |
| 90 | + |
| 91 | + services |
| 92 | + .AddScoped<ScopedServiceWithState>(); |
| 93 | + |
| 94 | + using (var serviceProvider = services.BuildServiceProvider()) |
| 95 | + { |
| 96 | + using (var scope = serviceProvider.CreateScope()) |
| 97 | + { |
| 98 | + var service1 = scope.ServiceProvider.GetRequiredService<ScopedServiceWithState>(); |
| 99 | + var service2 = scope.ServiceProvider.GetRequiredService<ScopedServiceWithState>(); |
| 100 | + |
| 101 | + // Assert: 在同一作用域内,Scoped 实例应相同 |
| 102 | + service1.ShouldBe(service2); |
| 103 | + |
| 104 | + using (var innerScope = scope.ServiceProvider.CreateScope()) |
| 105 | + { |
| 106 | + var innserService1 = innerScope.ServiceProvider.GetRequiredService<ScopedServiceWithState>(); |
| 107 | + var innserService2 = innerScope.ServiceProvider.GetRequiredService<ScopedServiceWithState>(); |
| 108 | + |
| 109 | + // Assert: 在新的作用域内,Scoped 实例应不同 |
| 110 | + innserService1.ShouldBe(innserService2); |
| 111 | + innserService1.ShouldNotBe(service1); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /// <summary> |
| 118 | + /// 测试 - 自动加载 - 作用域 |
| 119 | + /// </summary> |
| 120 | + [Fact] |
| 121 | + public void AutoLoad_Scoped() |
| 122 | + { |
| 123 | + var serviceCollection = new ServiceCollection(); |
| 124 | + serviceCollection.AddBing(); |
| 125 | + var serviceProvider = serviceCollection.BuildServiceProvider(); |
| 126 | + |
| 127 | + var scope1 = serviceProvider.CreateScope(); |
| 128 | + var a1= scope1.ServiceProvider.GetService<IA>(); |
| 129 | + Assert.True(a1 is D); |
| 130 | + |
| 131 | + var id1 = a1.Id; |
| 132 | + var a3 = scope1.ServiceProvider.GetRequiredService<IA>(); |
| 133 | + Assert.True(a3 is D); |
| 134 | + |
| 135 | + var id3 = a3.Id; |
| 136 | + scope1.Dispose(); |
| 137 | + Assert.Equal(id1, id3); |
| 138 | + |
| 139 | + var scope2 = serviceProvider.CreateScope(); |
| 140 | + var a2 = scope2.ServiceProvider.GetRequiredService<IA>(); |
| 141 | + Assert.True(a2 is D); |
| 142 | + |
| 143 | + var id2 = a2.Id; |
| 144 | + scope1.Dispose(); |
| 145 | + Assert.NotEqual(id1, id2); |
| 146 | + } |
| 147 | + |
| 148 | + /// <summary> |
| 149 | + /// 测试 - 自动加载 - 作用域 - 多个注入 |
| 150 | + /// </summary> |
| 151 | + [Fact] |
| 152 | + public void AutoLoad_Scoped_MultiInjection() |
| 153 | + { |
| 154 | + var serviceCollection = new ServiceCollection(); |
| 155 | + serviceCollection.AddBing(); |
| 156 | + var serviceProvider = serviceCollection.BuildServiceProvider(); |
| 157 | + |
| 158 | + var scope1 = serviceProvider.CreateScope(); |
| 159 | + var aList = scope1.ServiceProvider.GetServices<IA>(); |
| 160 | + Assert.Equal(2, aList.Count()); |
| 161 | + } |
| 162 | + |
| 163 | + /// <summary> |
| 164 | + /// 测试 - 自动加载 - 单例 |
| 165 | + /// </summary> |
| 166 | + [Fact] |
| 167 | + public void AutoLoad_Singleton() |
| 168 | + { |
| 169 | + var serviceCollection = new ServiceCollection(); |
| 170 | + serviceCollection.AddBing(); |
| 171 | + var serviceProvider = serviceCollection.BuildServiceProvider(); |
| 172 | + |
| 173 | + var b1 = serviceProvider.GetRequiredService<IB>(); |
| 174 | + Assert.True(b1 is B); |
| 175 | + |
| 176 | + var b2 = serviceProvider.GetRequiredService<IB>(); |
| 177 | + Assert.True(b2 is B); |
| 178 | + |
| 179 | + Assert.Equal(b1.Id, b2.Id); |
| 180 | + } |
| 181 | + |
| 182 | + /// <summary> |
| 183 | + /// 测试 - 自动加载 - 瞬时 |
| 184 | + /// </summary> |
| 185 | + [Fact] |
| 186 | + public void AutoLoad_Transient() |
| 187 | + { |
| 188 | + var serviceCollection = new ServiceCollection(); |
| 189 | + serviceCollection.AddBing(); |
| 190 | + var serviceProvider = serviceCollection.BuildServiceProvider(); |
| 191 | + |
| 192 | + var c1 = serviceProvider.GetRequiredService<IC>(); |
| 193 | + Assert.True(c1 is C); |
| 194 | + |
| 195 | + var c2 = serviceProvider.GetRequiredService<IC>(); |
| 196 | + Assert.True(c2 is C); |
| 197 | + |
| 198 | + Assert.NotEqual(c1.Id, c2.Id); |
| 199 | + } |
| 200 | + |
| 201 | + #region Samples |
| 202 | + |
| 203 | + /// <summary> |
| 204 | + /// 依赖单例的瞬时服务。 |
| 205 | + /// </summary> |
| 206 | + private class MyTransientServiceUsesSingleton |
| 207 | + { |
| 208 | + private readonly MySingletonServiceUsesTransients _singletonService; |
| 209 | + |
| 210 | + public MyTransientServiceUsesSingleton(MySingletonServiceUsesTransients singletonService) => _singletonService = singletonService; |
| 211 | + |
| 212 | + public void DoIt() => _singletonService.DoIt(); |
| 213 | + } |
| 214 | + |
| 215 | + /// <summary> |
| 216 | + /// 依赖多个瞬时服务的单例服务。 |
| 217 | + /// </summary> |
| 218 | + private class MySingletonServiceUsesTransients |
| 219 | + { |
| 220 | + private readonly IServiceProvider _serviceProvider; |
| 221 | + |
| 222 | + private readonly List<MyTransientService> _instances; |
| 223 | + |
| 224 | + public MySingletonServiceUsesTransients(IServiceProvider serviceProvider) |
| 225 | + { |
| 226 | + _serviceProvider = serviceProvider; |
| 227 | + _instances = new List<MyTransientService>(); |
| 228 | + } |
| 229 | + |
| 230 | + public void DoIt() => _instances.Add(_serviceProvider.GetRequiredService<MyTransientService>()); |
| 231 | + |
| 232 | + public void ShouldNotBeDisposed() |
| 233 | + { |
| 234 | + foreach (var instance in _instances) |
| 235 | + instance.IsDisposed.ShouldBeFalse(); |
| 236 | + } |
| 237 | + |
| 238 | + public void ShouldBeDisposed() |
| 239 | + { |
| 240 | + foreach (var instance in _instances) |
| 241 | + instance.IsDisposed.ShouldBeTrue(); |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + /// <summary> |
| 246 | + /// 瞬时服务,支持 IDisposable 以便测试是否被释放。 |
| 247 | + /// </summary> |
| 248 | + private class MyTransientService : IDisposable |
| 249 | + { |
| 250 | + public bool IsDisposed { get; private set; } |
| 251 | + |
| 252 | + public void Dispose() => IsDisposed = true; |
| 253 | + } |
| 254 | + |
| 255 | + /// <summary> |
| 256 | + /// 依赖多个瞬时服务的瞬时服务。 |
| 257 | + /// </summary> |
| 258 | + private class MyTransientServiceUsesTransients |
| 259 | + { |
| 260 | + private readonly IServiceProvider _serviceProvider; |
| 261 | + |
| 262 | + private readonly List<MyTransientService> _instances; |
| 263 | + |
| 264 | + public MyTransientServiceUsesTransients(IServiceProvider serviceProvider) |
| 265 | + { |
| 266 | + _serviceProvider = serviceProvider; |
| 267 | + _instances = new List<MyTransientService>(); |
| 268 | + } |
| 269 | + |
| 270 | + public void DoIt() => _instances.Add(_serviceProvider.GetRequiredService<MyTransientService>()); |
| 271 | + |
| 272 | + public void ShouldNotBeDisposed() |
| 273 | + { |
| 274 | + foreach (var instance in _instances) |
| 275 | + instance.IsDisposed.ShouldBeFalse(); |
| 276 | + } |
| 277 | + |
| 278 | + public void ShouldBeDisposed() |
| 279 | + { |
| 280 | + foreach (var instance in _instances) |
| 281 | + instance.IsDisposed.ShouldBeTrue(); |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + /// <summary> |
| 286 | + /// 具有状态的 Scoped 服务。 |
| 287 | + /// </summary> |
| 288 | + private class ScopedServiceWithState |
| 289 | + { |
| 290 | + private readonly Dictionary<string, object> _items; |
| 291 | + |
| 292 | + public ScopedServiceWithState() => _items = new Dictionary<string, object>(); |
| 293 | + |
| 294 | + public void Set(string name, object value) => _items[name] = value; |
| 295 | + |
| 296 | + public object Get(string name) => _items[name]; |
| 297 | + } |
| 298 | + |
| 299 | + private interface IA : IScopedDependency |
| 300 | + { |
| 301 | + string Id { get; } |
| 302 | + } |
| 303 | + |
| 304 | + private interface IB : ISingletonDependency |
| 305 | + { |
| 306 | + string Id { get; } |
| 307 | + } |
| 308 | + |
| 309 | + private interface IC : ITransientDependency |
| 310 | + { |
| 311 | + string Id { get; } |
| 312 | + } |
| 313 | + |
| 314 | + private class A : IA |
| 315 | + { |
| 316 | + public string Id { get; } = Guid.NewGuid().ToString(); |
| 317 | + } |
| 318 | + |
| 319 | + private class B : IB |
| 320 | + { |
| 321 | + public string Id { get; } = Guid.NewGuid().ToString(); |
| 322 | + } |
| 323 | + |
| 324 | + private class C : IC |
| 325 | + { |
| 326 | + public string Id { get; } = Guid.NewGuid().ToString(); |
| 327 | + } |
| 328 | + |
| 329 | + private class D : IA |
| 330 | + { |
| 331 | + public string Id { get; } = Guid.NewGuid().ToString(); |
| 332 | + } |
| 333 | + |
| 334 | + #endregion |
| 335 | +} |
0 commit comments