Skip to content

Commit 6574ca9

Browse files
authored
Enable custom extension of AutoSubstituteBuilder (#60)
This will allow users to extend with their own custom builders. This does two things: - Make a few private protected methods protected so things like the copy constructor is available. - Since some methods return a new builder, a new method GetCustomData is provided so that subclasses can store data that will be transferred to new instances
1 parent fbcf3e2 commit 6574ca9

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using NUnit.Framework;
2+
3+
namespace AutofacContrib.NSubstitute.Tests
4+
{
5+
[TestFixture]
6+
public sealed class CustomAutoSubstituteExtensionFixture
7+
{
8+
[Test]
9+
public void CusomExtensionCanAccessData()
10+
{
11+
var builder = AutoSubstitute.Configure();
12+
var custom1 = new CustomExtensionBuilder(builder);
13+
var substituted = builder.SubstituteFor<ITest>();
14+
var custom2 = new CustomExtensionBuilder(substituted);
15+
16+
Assert.AreSame(custom1.Data, custom2.Data);
17+
}
18+
19+
private class CustomExtensionBuilder : AutoSubstituteBuilder
20+
{
21+
public CustomExtensionBuilder(AutoSubstituteBuilder other)
22+
: base(other)
23+
{
24+
}
25+
26+
public CustomData Data => GetCustomData(() => new CustomData());
27+
}
28+
29+
private class CustomData
30+
{
31+
}
32+
33+
public interface ITest
34+
{
35+
}
36+
}
37+
}

AutofacContrib.NSubstitute/AutoSubstituteBuilder.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace AutofacContrib.NSubstitute
99
{
1010
public class AutoSubstituteBuilder
1111
{
12+
private readonly Dictionary<Type, object> _customDataManager;
1213
private readonly Dictionary<Type, object> _substituteForRegistrations;
1314
private readonly List<Action<IComponentContext>> _afterBuildActions;
1415
private readonly ContainerBuilder _builder;
@@ -20,18 +21,20 @@ public class AutoSubstituteBuilder
2021
public AutoSubstituteBuilder()
2122
{
2223
_substituteForRegistrations = new Dictionary<Type, object>();
24+
_customDataManager = new Dictionary<Type, object>();
2325
_afterBuildActions = new List<Action<IComponentContext>>();
2426
_builder = new ContainerBuilder();
2527
_options = new AutoSubstituteOptions();
2628
}
27-
29+
2830
/// <summary>
2931
/// Creates a new instance that allows linking to the previous instance for derived builders.
3032
/// </summary>
3133
/// <param name="other">A <see cref="AutoSubstituteBuilder"/> that should be connected to this instance</param>
32-
private protected AutoSubstituteBuilder(AutoSubstituteBuilder other)
34+
protected AutoSubstituteBuilder(AutoSubstituteBuilder other)
3335
{
3436
_substituteForRegistrations = other._substituteForRegistrations;
37+
_customDataManager = other._customDataManager;
3538
_afterBuildActions = other._afterBuildActions;
3639
_builder = other._builder;
3740
_options = other._options;
@@ -277,15 +280,53 @@ private SubstituteForBuilder<TService> CreateSubstituteForBuilder<TService>(Func
277280
return builder;
278281
}
279282

280-
private protected IProvidedValue<TService> CreateProvidedValue<TService>(Func<IComponentContext, TService> factory)
283+
/// <summary>
284+
/// Registers a callback for about <see cref="Build"/> is called.
285+
/// </summary>
286+
/// <param name="callback">Callback to call.</param>
287+
protected void RegisterBuildCallback(Action<IComponentContext> callback)
288+
=> _afterBuildActions.Add(callback);
289+
290+
/// <summary>
291+
/// Creates a delayed provided value with the given factory method.
292+
/// </summary>
293+
/// <typeparam name="TService">Service to expose for the provided value.</typeparam>
294+
/// <param name="factory">Factory to create value.</param>
295+
/// <returns>A provided value.</returns>
296+
protected IProvidedValue<TService> CreateProvidedValue<TService>(Func<IComponentContext, TService> factory)
281297
{
282298
var value = new ProvidedValue<TService>(factory);
283299

284-
_afterBuildActions.Add(c => value.SetComponentContext(c));
300+
RegisterBuildCallback(c => value.SetComponentContext(c));
285301

286302
return value;
287303
}
288304

305+
/// <summary>
306+
/// Retrieves storage of item that needs to be persisted across multiple calls to configuration methods.
307+
/// Some methods will wrap the builder into a new object, so this ensures that data used in one builder
308+
/// can be accessed later on.
309+
///
310+
/// Since the data is keyed by type, it is recommended to use a custom data type to hold the data if it
311+
/// is a well-known type.
312+
/// </summary>
313+
/// <typeparam name="T">Type of custom data.</typeparam>
314+
/// <param name="factory">Factory to create data.</param>
315+
/// <returns>A cached instance of the data, or a new instance if not already available.</returns>
316+
protected T GetCustomData<T>(Func<T> factory)
317+
{
318+
if (_customDataManager.TryGetValue(typeof(T), out var result))
319+
{
320+
return (T)result;
321+
}
322+
323+
var created = factory();
324+
325+
_customDataManager.Add(typeof(T), created);
326+
327+
return created;
328+
}
329+
289330
private class ProvidedValue<T> : IProvidedValue<T>
290331
{
291332
private readonly Func<IComponentContext, T> _factory;

0 commit comments

Comments
 (0)