Skip to content

Commit e25c6ac

Browse files
rinkebsafetyct-ownerseniorquico
authored
Use generic object for keyed services (#169)
Co-authored-by: Rinke Blootens <r.blootens@safetyct.com> Co-authored-by: Kyle Dodson <kyledodson@gmail.com>
1 parent 01d1388 commit e25c6ac

File tree

7 files changed

+212
-107
lines changed

7 files changed

+212
-107
lines changed

src/OrleansTestKit/Services/TestServiceProvider.cs

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,106 +5,87 @@
55

66
namespace Orleans.TestKit.Services;
77

8-
/// <summary>
9-
/// The test service provider
10-
/// </summary>
8+
/// <summary>The test service provider</summary>
119
public sealed class TestServiceProvider : IServiceProvider, IKeyedServiceProvider
1210
{
11+
private readonly Dictionary<(object?, Type), object> _keyedServices = new();
12+
1313
private readonly TestKitOptions _options;
14+
1415
private readonly Dictionary<Type, object> _services = new();
15-
private readonly Dictionary<(object?, Type), object> _keyedServices = new();
1616

17-
/// <summary>
18-
/// Initializes a new instance of the <see cref="TestServiceProvider"/> class.
19-
/// </summary>
17+
/// <summary>Initializes a new instance of the <see cref="TestServiceProvider"/> class.</summary>
2018
/// <param name="options">The test kit options to use</param>
2119
public TestServiceProvider(TestKitOptions options)
2220
{
2321
ArgumentNullException.ThrowIfNull(options);
2422
_options = options;
2523
}
2624

27-
/// <summary>
28-
/// Adds or updates a service to the provider
29-
/// </summary>
25+
/// <summary>Adds or updates a keyed service to the provider</summary>
3026
/// <typeparam name="T">The service type</typeparam>
27+
/// <param name="serviceKey">The service key</param>
3128
/// <param name="instance">The instance to add</param>
3229
/// <returns>The instance</returns>
3330
/// <exception cref="ArgumentNullException">Instance must be not null</exception>
34-
public T AddService<T>(T instance)
31+
public T AddKeyedService<T>(object? serviceKey, T instance)
3532
{
3633
ArgumentNullException.ThrowIfNull(instance);
3734

38-
_services[typeof(T)] = instance;
35+
_keyedServices[(serviceKey, typeof(T))] = instance;
3936
return instance;
4037
}
4138

42-
/// <summary>
43-
/// Adds or updates a keyed service to the provider
44-
/// </summary>
39+
/// <summary>Adds or updates a keyed mock to the service provider</summary>
40+
/// <typeparam name="T">The underlying service type</typeparam>
41+
/// <param name="serviceKey">The service key</param>
42+
/// <returns>The newly created mock</returns>
43+
public Mock<T> AddKeyedServiceProbe<T>(object? serviceKey)
44+
where T : class =>
45+
AddKeyedServiceProbe(serviceKey, new Mock<T>());
46+
47+
/// <summary>Adds or updates a keyed mock to the service provider</summary>
48+
/// <typeparam name="T">The underlying service type</typeparam>
49+
/// <param name="serviceKey">The service key</param>
50+
/// <param name="mock">The mock to add</param>
51+
/// <returns>The mock</returns>
52+
public Mock<T> AddKeyedServiceProbe<T>(object? serviceKey, Mock<T> mock)
53+
where T : class
54+
{
55+
AddKeyedService(serviceKey, mock.Object);
56+
return mock;
57+
}
58+
59+
/// <summary>Adds or updates a service to the provider</summary>
4560
/// <typeparam name="T">The service type</typeparam>
46-
/// <param name="name">The service key</param>
4761
/// <param name="instance">The instance to add</param>
4862
/// <returns>The instance</returns>
4963
/// <exception cref="ArgumentNullException">Instance must be not null</exception>
50-
public T AddKeyedService<T>(string name, T instance)
64+
public T AddService<T>(T instance)
5165
{
5266
ArgumentNullException.ThrowIfNull(instance);
5367

54-
_keyedServices[(name, typeof(T))] = instance;
68+
_services[typeof(T)] = instance;
5569
return instance;
5670
}
5771

58-
/// <summary>
59-
/// Adds a mock to the service provider
60-
/// </summary>
72+
/// <summary>Adds or updates a mock to the service provider</summary>
6173
/// <typeparam name="T">The underlying service type</typeparam>
6274
/// <param name="mock">The mock to add</param>
6375
/// <returns>The mock</returns>
64-
public Mock<T> AddServiceProbe<T>(Mock<T> mock) where T : class
76+
public Mock<T> AddServiceProbe<T>(Mock<T> mock)
77+
where T : class
6578
{
6679
AddService(mock.Object);
6780
return mock;
6881
}
6982

70-
/// <summary>
71-
/// Adds a mock to the service provider
72-
/// </summary>
83+
/// <summary>Adds a mock to the service provider</summary>
7384
/// <typeparam name="T">The underlying service type</typeparam>
7485
/// <returns>The newly created mock</returns>
75-
public Mock<T> AddServiceProbe<T>() where T : class => AddServiceProbe(new Mock<T>());
76-
77-
/// <inheritdoc/>
78-
public object GetService(Type serviceType)
79-
{
80-
ArgumentNullException.ThrowIfNull(serviceType);
81-
82-
if (_services.TryGetValue(serviceType, out var service))
83-
{
84-
return service;
85-
}
86-
87-
// If using strict service probes, throw the exception
88-
if (_options.StrictServiceProbes)
89-
{
90-
throw new Exception($"Service probe does not exist for type {serviceType.Name}. Ensure that it is added before the grain is tested.");
91-
}
92-
else
93-
{
94-
// Create a new mock
95-
if (Activator.CreateInstance(typeof(Mock<>).MakeGenericType(serviceType)) is not IMock<object> mock)
96-
{
97-
throw new Exception($"Failed to instantiate {serviceType.Name}.");
98-
}
99-
100-
service = mock.Object;
101-
102-
// Save the newly created grain for the next call
103-
_services.Add(serviceType, service);
104-
105-
return service;
106-
}
107-
}
86+
public Mock<T> AddServiceProbe<T>()
87+
where T : class =>
88+
AddServiceProbe(new Mock<T>());
10889

10990
public object? GetKeyedService(Type serviceType, object? serviceKey)
11091
{
@@ -155,4 +136,36 @@ public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
155136

156137
return service;
157138
}
139+
140+
/// <inheritdoc/>
141+
public object GetService(Type serviceType)
142+
{
143+
ArgumentNullException.ThrowIfNull(serviceType);
144+
145+
if (_services.TryGetValue(serviceType, out var service))
146+
{
147+
return service;
148+
}
149+
150+
// If using strict service probes, throw the exception
151+
if (_options.StrictServiceProbes)
152+
{
153+
throw new Exception($"Service probe does not exist for type {serviceType.Name}. Ensure that it is added before the grain is tested.");
154+
}
155+
else
156+
{
157+
// Create a new mock
158+
if (Activator.CreateInstance(typeof(Mock<>).MakeGenericType(serviceType)) is not IMock<object> mock)
159+
{
160+
throw new Exception($"Failed to instantiate {serviceType.Name}.");
161+
}
162+
163+
service = mock.Object;
164+
165+
// Save the newly created grain for the next call
166+
_services.Add(serviceType, service);
167+
168+
return service;
169+
}
170+
}
158171
}

src/OrleansTestKit/Services/TestServicesExtensions.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,47 @@ namespace Orleans.TestKit;
44

55
public static class TestServicesExtensions
66
{
7-
public static T AddService<T>(this TestKitSilo silo, T instance)
7+
public static T AddKeyedService<T>(this TestKitSilo silo, object? serviceKey, T instance)
8+
where T : class
9+
{
10+
if (silo == null)
11+
{
12+
throw new ArgumentNullException(nameof(silo));
13+
}
14+
15+
return silo.ServiceProvider.AddKeyedService(serviceKey, instance);
16+
}
17+
18+
public static Mock<T> AddKeyedServiceProbe<T>(this TestKitSilo silo, object? serviceKey)
19+
where T : class
20+
{
21+
if (silo == null)
22+
{
23+
throw new ArgumentNullException(nameof(silo));
24+
}
25+
26+
return silo.ServiceProvider.AddKeyedServiceProbe<T>(serviceKey);
27+
}
28+
29+
public static Mock<T> AddKeyedServiceProbe<T>(this TestKitSilo silo, object? serviceKey, Mock<T> mock)
830
where T : class
931
{
1032
if (silo == null)
1133
{
1234
throw new ArgumentNullException(nameof(silo));
1335
}
1436

37+
return silo.ServiceProvider.AddKeyedServiceProbe(serviceKey, mock);
38+
}
39+
40+
public static T AddService<T>(this TestKitSilo silo, T instance)
41+
where T : class
42+
{
43+
if (silo == null)
44+
{
45+
throw new ArgumentNullException(nameof(silo));
46+
}
47+
1548
return silo.ServiceProvider.AddService(instance);
1649
}
1750

test/OrleansTestKit.Tests/Grains/DIGrain.cs

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using TestInterfaces;
3+
4+
namespace TestGrains;
5+
6+
public interface IDependency
7+
{
8+
string? GetValue();
9+
}
10+
11+
public sealed class DependencyGrain : Grain, IDependencyGrain
12+
{
13+
private readonly IDependency? _firstKeyedDependency;
14+
15+
private readonly IDependency? _secondKeyedDependency;
16+
17+
private readonly IDependency? _unkeyedDependency;
18+
19+
public DependencyGrain(
20+
IDependency? unkeyedDependency,
21+
[FromKeyedServices("first")] IDependency? firstKeyedDependency,
22+
[FromKeyedServices("second")] IDependency? secondKeyedDependency)
23+
{
24+
_unkeyedDependency = unkeyedDependency;
25+
_firstKeyedDependency = firstKeyedDependency;
26+
_secondKeyedDependency = secondKeyedDependency;
27+
}
28+
29+
public Task<string?> GetFirstKeyedServiceValue() =>
30+
Task.FromResult(_firstKeyedDependency?.GetValue());
31+
32+
public Task<string?> GetSecondKeyedServiceValue() =>
33+
Task.FromResult(_secondKeyedDependency?.GetValue());
34+
35+
public Task<string?> GetUnkeyedServiceValue() =>
36+
Task.FromResult(_unkeyedDependency?.GetValue());
37+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TestInterfaces;
2+
3+
public interface IDependencyGrain : IGrainWithGuidKey
4+
{
5+
Task<string?> GetFirstKeyedServiceValue();
6+
7+
Task<string?> GetSecondKeyedServiceValue();
8+
9+
Task<string?> GetUnkeyedServiceValue();
10+
}

test/OrleansTestKit.Tests/Tests/DIGrainTests.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using FluentAssertions;
2+
using Moq;
3+
using TestGrains;
4+
using Xunit;
5+
6+
namespace Orleans.TestKit.Tests;
7+
8+
public class DependencyGrainTests : TestKitBase
9+
{
10+
[Fact]
11+
public async Task GrainWithoutServices()
12+
{
13+
// Arrange
14+
var grain = await Silo.CreateGrainAsync<DependencyGrain>(Guid.NewGuid());
15+
16+
// Act
17+
var unkeyedServiceValue = await grain.GetUnkeyedServiceValue();
18+
var firstKeyedServiceValue = await grain.GetFirstKeyedServiceValue();
19+
var secondKeyedServiceValue = await grain.GetSecondKeyedServiceValue();
20+
21+
// Assert
22+
unkeyedServiceValue.Should().BeNull();
23+
firstKeyedServiceValue.Should().BeNull();
24+
secondKeyedServiceValue.Should().BeNull();
25+
}
26+
27+
[Fact]
28+
public async Task GrainWithServices()
29+
{
30+
// Arrange
31+
var unkeyedService = new Mock<IDependency>(MockBehavior.Strict);
32+
unkeyedService.Setup(x => x.GetValue()).Returns("");
33+
Silo.ServiceProvider.AddServiceProbe(unkeyedService);
34+
35+
var firstKeyedService = new Mock<IDependency>(MockBehavior.Strict);
36+
firstKeyedService.Setup(x => x.GetValue()).Returns("first");
37+
Silo.ServiceProvider.AddKeyedServiceProbe("first", firstKeyedService);
38+
39+
var secondKeyedService = new Mock<IDependency>(MockBehavior.Strict);
40+
secondKeyedService.Setup(x => x.GetValue()).Returns("second");
41+
Silo.ServiceProvider.AddKeyedServiceProbe("second", secondKeyedService);
42+
43+
var grain = await Silo.CreateGrainAsync<DependencyGrain>(Guid.NewGuid());
44+
45+
// Act
46+
var unkeyedServiceValue = await grain.GetUnkeyedServiceValue();
47+
var firstKeyedServiceValue = await grain.GetFirstKeyedServiceValue();
48+
var secondKeyedServiceValue = await grain.GetSecondKeyedServiceValue();
49+
50+
// Assert
51+
unkeyedServiceValue.Should().BeEmpty();
52+
unkeyedService.Verify(x => x.GetValue(), Times.Once);
53+
54+
firstKeyedServiceValue.Should().Be("first");
55+
firstKeyedService.Verify(x => x.GetValue(), Times.Once);
56+
57+
secondKeyedServiceValue.Should().Be("second");
58+
secondKeyedService.Verify(x => x.GetValue(), Times.Once);
59+
}
60+
}

0 commit comments

Comments
 (0)