Skip to content

Commit d622284

Browse files
amirh-pkhellang
authored andcommitted
add tests and fix service key detection issue
1 parent 4e6e048 commit d622284

File tree

2 files changed

+333
-2
lines changed

2 files changed

+333
-2
lines changed

src/Scrutor/AttributeSelector.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis
3737

3838
foreach (var serviceType in serviceTypes)
3939
{
40-
var descriptor = new ServiceDescriptor(serviceType, attribute.ServiceKey, type, attribute.Lifetime);
40+
var descriptor = attribute.ServiceKey is null
41+
? new ServiceDescriptor(serviceType, type, attribute.Lifetime)
42+
: new ServiceDescriptor(serviceType, attribute.ServiceKey, type, attribute.Lifetime);
4143

4244
strategy.Apply(services, descriptor);
4345
}
@@ -47,6 +49,6 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis
4749

4850
private static IEnumerable<ServiceDescriptorAttribute> GetDuplicates(IEnumerable<ServiceDescriptorAttribute> attributes)
4951
{
50-
return attributes.GroupBy(s => s.ServiceType).SelectMany(grp => grp.Skip(1));
52+
return attributes.GroupBy(s => new { s.ServiceType, s.ServiceKey }).SelectMany(grp => grp.Skip(1));
5153
}
5254
}
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using System;
3+
using System.Linq;
4+
using Xunit;
5+
6+
namespace Scrutor.Tests;
7+
8+
public class KeyedServiceTests : TestBase
9+
{
10+
private IServiceCollection Collection { get; } = new ServiceCollection();
11+
12+
[Fact]
13+
public void CanRegisterKeyedServiceWithStringKey()
14+
{
15+
Collection.Scan(scan => scan
16+
.FromTypes(typeof(KeyedTransientService))
17+
.UsingAttributes());
18+
19+
Assert.Single(Collection);
20+
21+
var service = Collection.Single();
22+
Assert.Equal(typeof(IKeyedTestService), service.ServiceType);
23+
Assert.Equal(typeof(KeyedTransientService), service.KeyedImplementationType);
24+
Assert.Equal("test-key", service.ServiceKey);
25+
Assert.Equal(ServiceLifetime.Transient, service.Lifetime);
26+
Assert.True(service.IsKeyedService);
27+
}
28+
29+
[Fact]
30+
public void CanRegisterKeyedServiceWithGenericAttribute()
31+
{
32+
Collection.Scan(scan => scan
33+
.FromTypes(typeof(GenericKeyedService))
34+
.UsingAttributes());
35+
36+
Assert.Single(Collection);
37+
38+
var service = Collection.Single();
39+
Assert.Equal(typeof(IKeyedTestService), service.ServiceType);
40+
Assert.Equal(typeof(GenericKeyedService), service.KeyedImplementationType);
41+
Assert.Equal("generic-key", service.ServiceKey);
42+
Assert.Equal(ServiceLifetime.Scoped, service.Lifetime);
43+
Assert.True(service.IsKeyedService);
44+
}
45+
46+
[Fact]
47+
public void CanRegisterMultipleKeyedServicesOnSameType()
48+
{
49+
Collection.Scan(scan => scan
50+
.FromTypes(typeof(MultipleKeyedService))
51+
.UsingAttributes());
52+
53+
Assert.Equal(2, Collection.Count);
54+
55+
var services = Collection.ToArray();
56+
57+
var service1 = services.First(s => s.ServiceKey?.ToString() == "key1");
58+
Assert.Equal(typeof(IKeyedTestService), service1.ServiceType);
59+
Assert.Equal(typeof(MultipleKeyedService), service1.KeyedImplementationType);
60+
Assert.Equal(ServiceLifetime.Transient, service1.Lifetime);
61+
62+
var service2 = services.First(s => s.ServiceKey?.ToString() == "key2");
63+
Assert.Equal(typeof(IKeyedTestService), service2.ServiceType);
64+
Assert.Equal(typeof(MultipleKeyedService), service2.KeyedImplementationType);
65+
Assert.Equal(ServiceLifetime.Singleton, service2.Lifetime);
66+
}
67+
68+
[Fact]
69+
public void CanRegisterMixedKeyedAndNonKeyedServices()
70+
{
71+
Collection.Scan(scan => scan
72+
.FromTypes(typeof(MixedKeyedService))
73+
.UsingAttributes());
74+
75+
Assert.Equal(2, Collection.Count);
76+
77+
var keyedService = Collection.First(s => s.IsKeyedService);
78+
Assert.Equal(typeof(IKeyedTestService), keyedService.ServiceType);
79+
Assert.Equal(typeof(MixedKeyedService), keyedService.KeyedImplementationType);
80+
Assert.Equal("mixed-key", keyedService.ServiceKey);
81+
Assert.Equal(ServiceLifetime.Scoped, keyedService.Lifetime);
82+
83+
var nonKeyedService = Collection.First(s => !s.IsKeyedService);
84+
Assert.Equal(typeof(IKeyedTestService), nonKeyedService.ServiceType);
85+
Assert.Equal(typeof(MixedKeyedService), nonKeyedService.ImplementationType);
86+
Assert.Null(nonKeyedService.ServiceKey);
87+
Assert.Equal(ServiceLifetime.Transient, nonKeyedService.Lifetime);
88+
}
89+
90+
[Fact]
91+
public void CanResolveKeyedServices()
92+
{
93+
var provider = ConfigureProvider(services =>
94+
{
95+
services.Scan(scan => scan
96+
.FromTypes(typeof(KeyedTransientService), typeof(GenericKeyedService), typeof(MultipleKeyedService))
97+
.UsingAttributes());
98+
});
99+
100+
var keyedTransient = provider.GetRequiredKeyedService<IKeyedTestService>("test-key");
101+
Assert.IsType<KeyedTransientService>(keyedTransient);
102+
103+
using var scope = provider.CreateScope();
104+
var genericKeyed = scope.ServiceProvider.GetRequiredKeyedService<IKeyedTestService>("generic-key");
105+
Assert.IsType<GenericKeyedService>(genericKeyed);
106+
107+
var multipleKeyed1 = provider.GetRequiredKeyedService<IKeyedTestService>("key1");
108+
Assert.IsType<MultipleKeyedService>(multipleKeyed1);
109+
110+
var multipleKeyed2 = provider.GetRequiredKeyedService<IKeyedTestService>("key2");
111+
Assert.IsType<MultipleKeyedService>(multipleKeyed2);
112+
113+
// Verify they are different instances for transient services
114+
var anotherKeyedTransient = provider.GetRequiredKeyedService<IKeyedTestService>("test-key");
115+
Assert.NotSame(keyedTransient, anotherKeyedTransient);
116+
117+
// Verify singleton behavior
118+
var anotherMultipleKeyed2 = provider.GetRequiredKeyedService<IKeyedTestService>("key2");
119+
Assert.Same(multipleKeyed2, anotherMultipleKeyed2);
120+
}
121+
122+
[Fact]
123+
public void KeyedServicesAreIsolatedFromNonKeyedServices()
124+
{
125+
var provider = ConfigureProvider(services =>
126+
{
127+
services.Scan(scan => scan
128+
.FromTypes(typeof(MixedKeyedService))
129+
.UsingAttributes());
130+
});
131+
132+
using var scope = provider.CreateScope();
133+
var keyedService = scope.ServiceProvider.GetRequiredKeyedService<IKeyedTestService>("mixed-key");
134+
var nonKeyedService = provider.GetRequiredService<IKeyedTestService>();
135+
136+
Assert.IsType<MixedKeyedService>(keyedService);
137+
Assert.IsType<MixedKeyedService>(nonKeyedService);
138+
Assert.NotSame(keyedService, nonKeyedService);
139+
}
140+
141+
[Fact]
142+
public void CanRegisterKeyedServiceWithObjectKey()
143+
{
144+
Collection.Scan(scan => scan
145+
.FromTypes(typeof(ObjectKeyedService))
146+
.UsingAttributes());
147+
148+
Assert.Single(Collection);
149+
150+
var service = Collection.Single();
151+
Assert.Equal(typeof(IKeyedTestService), service.ServiceType);
152+
Assert.Equal(typeof(ObjectKeyedService), service.KeyedImplementationType);
153+
Assert.Equal(42, service.ServiceKey);
154+
Assert.True(service.IsKeyedService);
155+
}
156+
157+
[Fact]
158+
public void CanResolveKeyedServiceWithObjectKey()
159+
{
160+
var provider = ConfigureProvider(services =>
161+
{
162+
services.Scan(scan => scan
163+
.FromTypes(typeof(ObjectKeyedService))
164+
.UsingAttributes());
165+
});
166+
167+
var keyedService = provider.GetRequiredKeyedService<IKeyedTestService>(42);
168+
Assert.IsType<ObjectKeyedService>(keyedService);
169+
}
170+
171+
[Fact]
172+
public void CanRegisterKeyedServiceWithEnumKey()
173+
{
174+
Collection.Scan(scan => scan
175+
.FromTypes(typeof(EnumKeyedService))
176+
.UsingAttributes());
177+
178+
Assert.Single(Collection);
179+
180+
var service = Collection.Single();
181+
Assert.Equal(typeof(IKeyedTestService), service.ServiceType);
182+
Assert.Equal(typeof(EnumKeyedService), service.KeyedImplementationType);
183+
Assert.Equal(TestEnum.Value1, service.ServiceKey);
184+
Assert.True(service.IsKeyedService);
185+
}
186+
187+
[Fact]
188+
public void CanRegisterKeyedServiceWithDifferentServiceTypes()
189+
{
190+
Collection.Scan(scan => scan
191+
.FromTypes(typeof(MultiServiceKeyedService))
192+
.UsingAttributes());
193+
194+
Assert.Equal(2, Collection.Count);
195+
196+
var keyedService = Collection.First(s => s.ServiceType == typeof(IKeyedTestService));
197+
Assert.Equal(typeof(MultiServiceKeyedService), keyedService.KeyedImplementationType);
198+
Assert.Equal("service-key", keyedService.ServiceKey);
199+
200+
var otherKeyedService = Collection.First(s => s.ServiceType == typeof(IOtherKeyedTestService));
201+
Assert.Equal(typeof(MultiServiceKeyedService), otherKeyedService.KeyedImplementationType);
202+
Assert.Equal("other-key", otherKeyedService.ServiceKey);
203+
}
204+
205+
[Fact]
206+
public void ThrowsWhenResolvingNonExistentKeyedService()
207+
{
208+
var provider = ConfigureProvider(services =>
209+
{
210+
services.Scan(scan => scan
211+
.FromTypes(typeof(KeyedTransientService))
212+
.UsingAttributes());
213+
});
214+
215+
Assert.Throws<InvalidOperationException>(() =>
216+
provider.GetRequiredKeyedService<IKeyedTestService>("non-existent-key"));
217+
}
218+
219+
[Fact]
220+
public void CanRegisterKeyedServiceWithNullServiceType()
221+
{
222+
Collection.Scan(scan => scan
223+
.FromTypes(typeof(KeyedServiceWithNullServiceType))
224+
.UsingAttributes());
225+
226+
// Should register for the implementation type and all its interfaces
227+
Assert.Equal(2, Collection.Count); // IKeyedTestService and KeyedServiceWithNullServiceType itself
228+
229+
var services = Collection.ToArray();
230+
Assert.All(services, s =>
231+
{
232+
Assert.Equal("null-service-type-key", s.ServiceKey);
233+
Assert.True(s.IsKeyedService);
234+
});
235+
}
236+
237+
238+
[Fact]
239+
public void AllowsSameServiceTypeWithDifferentKeys()
240+
{
241+
Collection.Scan(scan => scan
242+
.FromTypes(typeof(SameServiceTypeDifferentKeys))
243+
.UsingAttributes());
244+
245+
Assert.Equal(2, Collection.Count);
246+
247+
var service1 = Collection.First(s => s.ServiceKey?.ToString() == "key1");
248+
var service2 = Collection.First(s => s.ServiceKey?.ToString() == "key2");
249+
250+
Assert.Equal(typeof(IKeyedTestService), service1.ServiceType);
251+
Assert.Equal(typeof(IKeyedTestService), service2.ServiceType);
252+
Assert.NotEqual(service1.ServiceKey, service2.ServiceKey);
253+
}
254+
255+
[Fact]
256+
public void CanRegisterServiceWithNullKey()
257+
{
258+
Collection.Scan(scan => scan
259+
.FromTypes(typeof(NullKeyedService))
260+
.UsingAttributes());
261+
262+
Assert.Single(Collection);
263+
264+
var service = Collection.Single();
265+
Assert.Equal(typeof(IKeyedTestService), service.ServiceType);
266+
Assert.Equal(typeof(NullKeyedService), service.ImplementationType);
267+
Assert.Null(service.ServiceKey);
268+
Assert.False(service.IsKeyedService);
269+
}
270+
271+
[Fact]
272+
public void CanResolveServiceWithNullKey()
273+
{
274+
var provider = ConfigureProvider(services =>
275+
{
276+
services.Scan(scan => scan
277+
.FromTypes(typeof(NullKeyedService))
278+
.UsingAttributes());
279+
});
280+
281+
var service = provider.GetRequiredService<IKeyedTestService>();
282+
Assert.IsType<NullKeyedService>(service);
283+
}
284+
}
285+
286+
// Test interfaces and classes for keyed services
287+
public interface IKeyedTestService { }
288+
public interface IOtherKeyedTestService { }
289+
290+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, "test-key")]
291+
public class KeyedTransientService : IKeyedTestService { }
292+
293+
[ServiceDescriptor<IKeyedTestService>(ServiceLifetime.Scoped, "generic-key")]
294+
public class GenericKeyedService : IKeyedTestService { }
295+
296+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, "key1")]
297+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Singleton, "key2")]
298+
public class MultipleKeyedService : IKeyedTestService { }
299+
300+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Scoped, "mixed-key")]
301+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient)]
302+
public class MixedKeyedService : IKeyedTestService { }
303+
304+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, 42)]
305+
public class ObjectKeyedService : IKeyedTestService { }
306+
307+
public enum TestEnum
308+
{
309+
Value1,
310+
Value2
311+
}
312+
313+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, TestEnum.Value1)]
314+
public class EnumKeyedService : IKeyedTestService { }
315+
316+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, "service-key")]
317+
[ServiceDescriptor(typeof(IOtherKeyedTestService), ServiceLifetime.Scoped, "other-key")]
318+
public class MultiServiceKeyedService : IKeyedTestService, IOtherKeyedTestService { }
319+
320+
[ServiceDescriptor(null, ServiceLifetime.Transient, "null-service-type-key")]
321+
public class KeyedServiceWithNullServiceType : IKeyedTestService { }
322+
323+
324+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, "key1")]
325+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Scoped, "key2")]
326+
public class SameServiceTypeDifferentKeys : IKeyedTestService { }
327+
328+
[ServiceDescriptor(typeof(IKeyedTestService), ServiceLifetime.Transient, null)]
329+
public class NullKeyedService : IKeyedTestService { }

0 commit comments

Comments
 (0)