Skip to content

Commit 93c4c25

Browse files
committed
Feat: Add support for assertions on keyed service registrations
1 parent 79fa2a7 commit 93c4c25

File tree

4 files changed

+190
-5
lines changed

4 files changed

+190
-5
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using Microsoft.Extensions.DependencyInjection;
5+
using TUnit.Assertions.AssertConditions;
6+
7+
namespace CodeOfChaos.Testing.TUnit.Conditions.Library;
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
// Code
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
public class ContainsKeyedServiceTypeCondition(Type serviceType, object? key) : BaseAssertCondition<IServiceCollection> {
12+
// -----------------------------------------------------------------------------------------------------------------
13+
// Methods
14+
// -----------------------------------------------------------------------------------------------------------------
15+
protected override string GetExpectation() => $"to have a registered keyed service of type \"{serviceType.Name}\" with key \"{key}\"";
16+
17+
protected override ValueTask<AssertionResult> GetResult(IServiceCollection? actualValue, Exception? exception, AssertionMetadata assertionMetadata) {
18+
if (actualValue is null)
19+
return AssertionResult.Fail($"{nameof(IServiceCollection)} is null");
20+
21+
return actualValue.Any(descriptor =>
22+
descriptor.IsKeyedService
23+
&& descriptor.ServiceType == serviceType
24+
&& ReferenceEquals(descriptor.ServiceKey, key)
25+
)
26+
? AssertionResult.Passed
27+
: FailWithMessage($"No keyed service with type {serviceType.Name} and key {key} has been registered.");
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using Microsoft.Extensions.DependencyInjection;
5+
using TUnit.Assertions.AssertConditions;
6+
7+
namespace CodeOfChaos.Testing.TUnit.Conditions.Library;
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
// Code
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
public class DoesNotContainKeyedServiceTypeCondition(Type serviceType, object? key) : BaseAssertCondition<IServiceCollection> {
12+
// -----------------------------------------------------------------------------------------------------------------
13+
// Methods
14+
// -----------------------------------------------------------------------------------------------------------------
15+
protected override string GetExpectation() => $"to not have a registered keyed service of type \"{serviceType.Name}\" with key \"{key}\"";
16+
17+
protected override ValueTask<AssertionResult> GetResult(IServiceCollection? actualValue, Exception? exception, AssertionMetadata assertionMetadata) {
18+
if (actualValue is null)
19+
return AssertionResult.Fail($"{nameof(IServiceCollection)} is null");
20+
21+
return actualValue.Any(descriptor =>
22+
descriptor.IsKeyedService
23+
&& descriptor.ServiceType == serviceType
24+
&& ReferenceEquals(descriptor.ServiceKey, key)
25+
)
26+
? FailWithMessage($"Found service of type {serviceType.Name} registered with key {key}")
27+
: AssertionResult.Passed;
28+
}
29+
}

src/CodeOfChaos.Testing.TUnit/Extensions/TUnitExtensionsServiceCollection.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public static InvokableValueAssertionBuilder<IServiceCollection> ContainsService
3838
this IValueSource<IServiceCollection> valueSource,
3939
Type serviceType,
4040
Type implementationType,
41-
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = ""
41+
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(implementationType))] string doNotPopulateThisValue2 = ""
4242
)
43-
=> valueSource.RegisterAssertion(new ContainsServiceImplementationCondition(serviceType, implementationType), [doNotPopulateThisValue1]);
43+
=> valueSource.RegisterAssertion(new ContainsServiceImplementationCondition(serviceType, implementationType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
4444

4545
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainServiceImplementation<TService, TImplementation>(
4646
this IValueSource<IServiceCollection> valueSource,
@@ -52,10 +52,22 @@ public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainS
5252
this IValueSource<IServiceCollection> valueSource,
5353
Type serviceType,
5454
Type implementationType,
55-
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = ""
55+
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(implementationType))] string doNotPopulateThisValue2 = ""
5656
)
57-
=> valueSource.RegisterAssertion(new DoesNotContainServiceImplementationCondition(serviceType, implementationType), [doNotPopulateThisValue1]);
57+
=> valueSource.RegisterAssertion(new DoesNotContainServiceImplementationCondition(serviceType, implementationType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
5858
#endregion
5959

60-
60+
#region ContainsKeyedService
61+
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceType<TServiceType>(this IValueSource<IServiceCollection> valueSource, object? key, [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = "")
62+
=> valueSource.RegisterAssertion(new ContainsKeyedServiceTypeCondition(typeof(TServiceType), key), [doNotPopulateThisValue1]);
63+
64+
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceType(this IValueSource<IServiceCollection> valueSource, Type serviceType, object? key, [CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue2 = "")
65+
=> valueSource.RegisterAssertion(new ContainsServiceTypeCondition(serviceType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
66+
67+
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceType<TServiceType>(this IValueSource<IServiceCollection> valueSource, object? key, [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = "")
68+
=> valueSource.RegisterAssertion(new DoesNotContainKeyedServiceTypeCondition(typeof(TServiceType), key), [doNotPopulateThisValue1]);
69+
70+
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceType(this IValueSource<IServiceCollection> valueSource, Type serviceType, object? key, [CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue2 = "")
71+
=> valueSource.RegisterAssertion(new DoesNotContainServiceTypeCondition(serviceType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
72+
#endregion
6173
}

tests/Tests.CodeOfChaos.Testing.TUnit/TUnitExtensionsServiceCollectionTests.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,119 @@ await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(servi
222222
}
223223
#endregion
224224

225+
#region ContainsKeyedService
226+
[Test]
227+
public async Task ContainsKeyedServiceType_ShouldAssert() {
228+
// Arrange
229+
var services = new ServiceCollection();
230+
const string key = "test-key";
231+
232+
// Act
233+
services.AddKeyedSingleton<IService, Service>(key);
234+
235+
// Assert
236+
await Assert.That(services).ContainsKeyedServiceType<IService>(key);
237+
}
238+
239+
[Test]
240+
[Arguments(typeof(IService), typeof(Service))]
241+
public async Task ContainsKeyedServiceType_ShouldAssert_FromTypes(Type service, Type implementation) {
242+
// Arrange
243+
var services = new ServiceCollection();
244+
const string key = "test-key";
245+
246+
// Act
247+
services.AddKeyedSingleton(service, implementation, key);
248+
249+
// Assert
250+
await Assert.That(services).ContainsKeyedServiceType(service, key);
251+
}
252+
253+
[Test]
254+
public async Task ContainsKeyedServiceType_ShouldThrowAssertion_WhenServiceNotFound() {
255+
// Arrange
256+
var services = new ServiceCollection();
257+
const string key = "test-key";
258+
259+
// Act
260+
services.AddKeyedSingleton<IService, Service>(key);
261+
262+
// Assert
263+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).ContainsKeyedServiceType<int>(key)
264+
);
265+
}
266+
267+
[Test]
268+
public async Task ContainsKeyedServiceType_ShouldThrowAssertion_WhenKeyNotFound() {
269+
// Arrange
270+
var services = new ServiceCollection();
271+
const string key = "test-key";
272+
const string wrongKey = "wrong-key";
273+
274+
// Act
275+
services.AddKeyedSingleton<IService, Service>(key);
276+
277+
// Assert
278+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).ContainsKeyedServiceType<IService>(wrongKey)
279+
);
280+
}
281+
282+
[Test]
283+
public async Task DoesNotContainKeyedServiceType_ShouldAssert() {
284+
// Arrange
285+
var services = new ServiceCollection();
286+
const string key = "test-key";
287+
288+
// Act
289+
services.AddKeyedSingleton<IService, Service>(key);
290+
291+
// Assert
292+
await Assert.That(services).DoesNotContainKeyedServiceType<int>(key);
293+
}
294+
295+
[Test]
296+
[Arguments(typeof(IService), typeof(Service))]
297+
public async Task DoesNotContainKeyedServiceType_ShouldAssert_FromTypes(Type service, Type implementation) {
298+
// Arrange
299+
var services = new ServiceCollection();
300+
const string key = "test-key";
301+
302+
// Act
303+
services.AddKeyedSingleton(service, implementation, key);
304+
305+
// Assert
306+
await Assert.That(services).DoesNotContainKeyedServiceType(typeof(int), key);
307+
}
308+
309+
[Test]
310+
public async Task DoesNotContainKeyedServiceType_ShouldThrowAssertion_WhenServiceExists() {
311+
// Arrange
312+
var services = new ServiceCollection();
313+
const string key = "test-key";
314+
315+
// Act
316+
services.AddKeyedSingleton<IService, Service>(key);
317+
318+
// Assert
319+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).DoesNotContainKeyedServiceType<IService>(key)
320+
);
321+
}
322+
323+
[Test]
324+
[Arguments(typeof(IService), typeof(Service))]
325+
public async Task DoesNotContainKeyedServiceType_ShouldThrowAssertion_FromTypes_WhenServiceExists(
326+
Type service, Type implementation
327+
) {
328+
// Arrange
329+
var services = new ServiceCollection();
330+
const string key = "test-key";
331+
332+
// Act
333+
services.AddKeyedSingleton(service, implementation, key);
334+
335+
// Assert
336+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).DoesNotContainKeyedServiceType(service, key));
337+
}
338+
#endregion
339+
225340
}

0 commit comments

Comments
 (0)