Skip to content

Commit 0d5fa2c

Browse files
committed
Feat: Add support for keyed service implementation assertions
1 parent 93c4c25 commit 0d5fa2c

File tree

4 files changed

+256
-4
lines changed

4 files changed

+256
-4
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 ContainsKeyedServiceImplementationCondition(Type serviceType, Type implementationType, object? key)
12+
: BaseAssertCondition<IServiceCollection>
13+
{
14+
protected override string GetExpectation() =>
15+
$"to have a registered keyed service of type \"{serviceType.Name}\" implemented by \"{implementationType.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(Predicate)
22+
? AssertionResult.Passed
23+
: FailWithMessage($"No keyed service with type {serviceType.Name}, implementation {implementationType.Name}, and key {key} has been registered.");
24+
}
25+
26+
private bool Predicate(ServiceDescriptor descriptor) {
27+
return descriptor.IsKeyedService
28+
&& descriptor.ServiceType == serviceType
29+
&& descriptor.KeyedImplementationType == implementationType
30+
&& ReferenceEquals(descriptor.ServiceKey, key);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 DoesNotContainKeyedServiceImplementationCondition(Type serviceType, Type implementationType, object? key)
12+
: BaseAssertCondition<IServiceCollection>
13+
{
14+
protected override string GetExpectation() =>
15+
$"to not have a registered keyed service of type \"{serviceType.Name}\" implemented by \"{implementationType.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(Predicate)
22+
? FailWithMessage($"Found keyed service of type {serviceType.Name}, implementation {implementationType.Name} registered with key {key}")
23+
: AssertionResult.Passed;
24+
}
25+
26+
private bool Predicate(ServiceDescriptor descriptor) {
27+
return descriptor.IsKeyedService
28+
&& descriptor.ServiceType == serviceType
29+
&& descriptor.KeyedImplementationType == implementationType
30+
&& ReferenceEquals(descriptor.ServiceKey, key);
31+
}
32+
}

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

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static InvokableValueAssertionBuilder<IServiceCollection> ContainsService
3333
[CallerArgumentExpression(nameof(valueSource))] string doNotPopulateThisValue1 = ""
3434
)
3535
=> valueSource.RegisterAssertion(new ContainsServiceImplementationCondition(typeof(TService), typeof(TImplementation)), [doNotPopulateThisValue1]);
36-
36+
3737
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsServiceImplementation(
3838
this IValueSource<IServiceCollection> valueSource,
3939
Type serviceType,
@@ -60,14 +60,56 @@ public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainS
6060
#region ContainsKeyedService
6161
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceType<TServiceType>(this IValueSource<IServiceCollection> valueSource, object? key, [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = "")
6262
=> valueSource.RegisterAssertion(new ContainsKeyedServiceTypeCondition(typeof(TServiceType), key), [doNotPopulateThisValue1]);
63-
63+
6464
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceType(this IValueSource<IServiceCollection> valueSource, Type serviceType, object? key, [CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue2 = "")
6565
=> valueSource.RegisterAssertion(new ContainsServiceTypeCondition(serviceType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
66-
66+
6767
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceType<TServiceType>(this IValueSource<IServiceCollection> valueSource, object? key, [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = "")
6868
=> valueSource.RegisterAssertion(new DoesNotContainKeyedServiceTypeCondition(typeof(TServiceType), key), [doNotPopulateThisValue1]);
69-
69+
7070
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceType(this IValueSource<IServiceCollection> valueSource, Type serviceType, object? key, [CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "", [CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue2 = "")
7171
=> valueSource.RegisterAssertion(new DoesNotContainServiceTypeCondition(serviceType), [doNotPopulateThisValue1, doNotPopulateThisValue2]);
7272
#endregion
73+
74+
#region ContainsKeyedServiceImplementation
75+
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceImplementation<TService, TImplementation>(
76+
this IValueSource<IServiceCollection> valueSource,
77+
object? key,
78+
[CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = ""
79+
)
80+
=> valueSource.RegisterAssertion(new ContainsKeyedServiceImplementationCondition(typeof(TService), typeof(TImplementation), key), [doNotPopulateThisValue1]);
81+
82+
public static InvokableValueAssertionBuilder<IServiceCollection> ContainsKeyedServiceImplementation(
83+
this IValueSource<IServiceCollection> valueSource,
84+
Type serviceType,
85+
Type implementationType,
86+
object? key,
87+
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "",
88+
[CallerArgumentExpression(nameof(implementationType))] string doNotPopulateThisValue2 = "",
89+
[CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue3 = ""
90+
)
91+
=> valueSource.RegisterAssertion(new ContainsKeyedServiceImplementationCondition(serviceType, implementationType, key),
92+
[doNotPopulateThisValue1, doNotPopulateThisValue2, doNotPopulateThisValue3]);
93+
94+
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceImplementation<TService, TImplementation>(
95+
this IValueSource<IServiceCollection> valueSource,
96+
object? key,
97+
[CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue1 = ""
98+
)
99+
=> valueSource.RegisterAssertion(new DoesNotContainKeyedServiceImplementationCondition(typeof(TService), typeof(TImplementation), key),
100+
[doNotPopulateThisValue1]);
101+
102+
public static InvokableValueAssertionBuilder<IServiceCollection> DoesNotContainKeyedServiceImplementation(
103+
this IValueSource<IServiceCollection> valueSource,
104+
Type serviceType,
105+
Type implementationType,
106+
object? key,
107+
[CallerArgumentExpression(nameof(serviceType))] string doNotPopulateThisValue1 = "",
108+
[CallerArgumentExpression(nameof(implementationType))] string doNotPopulateThisValue2 = "",
109+
[CallerArgumentExpression(nameof(key))] string doNotPopulateThisValue3 = ""
110+
)
111+
=> valueSource.RegisterAssertion(
112+
new DoesNotContainKeyedServiceImplementationCondition(serviceType, implementationType, key),
113+
[doNotPopulateThisValue1, doNotPopulateThisValue2, doNotPopulateThisValue3]);
114+
#endregion
73115
}

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

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,4 +337,150 @@ public async Task DoesNotContainKeyedServiceType_ShouldThrowAssertion_FromTypes_
337337
}
338338
#endregion
339339

340+
#region ContainsKeyedServiceImplementation
341+
[Test]
342+
public async Task ContainsKeyedServiceImplementation_ShouldAssert() {
343+
// Arrange
344+
var services = new ServiceCollection();
345+
const string key = "test-key";
346+
347+
// Act
348+
services.AddKeyedSingleton<IService, Service>(key);
349+
350+
// Assert
351+
await Assert.That(services).ContainsKeyedServiceImplementation<IService, Service>(key);
352+
}
353+
354+
[Test]
355+
[Arguments(typeof(IService), typeof(Service))]
356+
public async Task ContainsKeyedServiceImplementation_ShouldAssert_FromTypes(Type service, Type implementation) {
357+
// Arrange
358+
var services = new ServiceCollection();
359+
const string key = "test-key";
360+
361+
// Act
362+
services.AddKeyedSingleton(service, key, implementation);
363+
364+
// Assert
365+
await Assert.That(services).ContainsKeyedServiceImplementation(service, implementation, key);
366+
}
367+
368+
[Test]
369+
public async Task ContainsKeyedServiceImplementation_ShouldThrowAssertion_WhenServiceNotFound() {
370+
// Arrange
371+
var services = new ServiceCollection();
372+
const string key = "test-key";
373+
374+
// Act
375+
services.AddKeyedSingleton<IService, Service>(key);
376+
377+
// Assert
378+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).ContainsKeyedServiceImplementation<int, int>(key)
379+
);
380+
}
381+
382+
[Test]
383+
public async Task ContainsKeyedServiceImplementation_ShouldThrowAssertion_WhenKeyNotFound() {
384+
// Arrange
385+
var services = new ServiceCollection();
386+
const string key = "test-key";
387+
const string wrongKey = "wrong-key";
388+
389+
// Act
390+
services.AddKeyedSingleton<IService, Service>(key);
391+
392+
// Assert
393+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).ContainsKeyedServiceImplementation<IService, Service>(wrongKey)
394+
);
395+
}
396+
397+
[Test]
398+
public async Task ContainsKeyedServiceImplementation_ShouldThrowAssertion_WhenWrongImplementation() {
399+
// Arrange
400+
var services = new ServiceCollection();
401+
const string key = "test-key";
402+
403+
// Act
404+
services.AddKeyedSingleton<IService, Service>(key);
405+
406+
// Assert
407+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).ContainsKeyedServiceImplementation<IService, IService>(key)
408+
);
409+
}
410+
#endregion
411+
412+
#region DoesNotContainKeyedServiceImplementation
413+
[Test]
414+
public async Task DoesNotContainKeyedServiceImplementation_ShouldAssert() {
415+
// Arrange
416+
var services = new ServiceCollection();
417+
const string key = "test-key";
418+
419+
// Act
420+
services.AddKeyedSingleton<IService, Service>(key);
421+
422+
// Assert
423+
await Assert.That(services).DoesNotContainKeyedServiceImplementation<int, int>(key);
424+
}
425+
426+
[Test]
427+
[Arguments(typeof(IService), typeof(Service))]
428+
public async Task DoesNotContainKeyedServiceImplementation_ShouldAssert_FromTypes(Type service, Type implementation) {
429+
// Arrange
430+
var services = new ServiceCollection();
431+
const string key = "test-key";
432+
433+
// Act
434+
services.AddKeyedSingleton(service, implementation, key);
435+
436+
// Assert
437+
await Assert.That(services).DoesNotContainKeyedServiceImplementation(typeof(int), typeof(int), key);
438+
}
439+
440+
[Test]
441+
public async Task DoesNotContainKeyedServiceImplementation_ShouldThrowAssertion_WhenFound() {
442+
// Arrange
443+
var services = new ServiceCollection();
444+
const string key = "test-key";
445+
446+
// Act
447+
services.AddKeyedSingleton<IService, Service>(key);
448+
449+
// Assert
450+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).DoesNotContainKeyedServiceImplementation<IService, Service>(key)
451+
);
452+
}
453+
454+
[Test]
455+
[Arguments(typeof(IService), typeof(Service))]
456+
public async Task DoesNotContainKeyedServiceImplementation_ShouldThrowAssertion_FromTypes_WhenFound(
457+
Type service, Type implementation
458+
) {
459+
// Arrange
460+
var services = new ServiceCollection();
461+
const string key = "test-key";
462+
463+
// Act
464+
services.AddKeyedSingleton(service, key, implementation);
465+
466+
// Assert
467+
await Assert.ThrowsAsync<AssertionException>(async () => await Assert.That(services).DoesNotContainKeyedServiceImplementation(service, implementation, key)
468+
);
469+
}
470+
471+
[Test]
472+
public async Task DoesNotContainKeyedServiceImplementation_ShouldAssert_WhenDifferentKey() {
473+
// Arrange
474+
var services = new ServiceCollection();
475+
const string key = "test-key";
476+
const string differentKey = "different-key";
477+
478+
// Act
479+
services.AddKeyedSingleton<IService, Service>(key);
480+
481+
// Assert
482+
await Assert.That(services).DoesNotContainKeyedServiceImplementation<IService, Service>(differentKey);
483+
}
484+
#endregion
485+
340486
}

0 commit comments

Comments
 (0)