Skip to content

Commit 9b2c921

Browse files
authored
Add helper method to disable base calls on substitutes (#47)
1 parent 127c7f5 commit 9b2c921

9 files changed

+229
-38
lines changed

AutofacContrib.NSubstitute.Tests/AutoSubstituteOptionsFixture.cs

+28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Autofac;
22
using Autofac.Core;
33
using NUnit.Framework;
4+
using System;
45

56
namespace AutofacContrib.NSubstitute.Tests
67
{
@@ -108,6 +109,33 @@ public void InternalConstructorSucceeds()
108109
Assert.NotNull(mock.Resolve<ClassWithInternalConstructor>());
109110
}
110111

112+
[Test]
113+
public void BaseCalledByDefault()
114+
{
115+
using var mock = AutoSubstitute.Configure()
116+
.SubstituteForPartsOf<ClassWithBase>().Configured()
117+
.Build()
118+
.Container;
119+
120+
Assert.Throws<InvalidOperationException>(() => mock.Resolve<ClassWithBase>().Throws());
121+
}
122+
123+
[Test]
124+
public void BaseCallDisabled()
125+
{
126+
using var mock = AutoSubstitute.Configure()
127+
.SubstituteForPartsOf<ClassWithBase>().DoNotCallBase().Configured()
128+
.Build()
129+
.Container;
130+
131+
Assert.Null(mock.Resolve<ClassWithBase>().Throws());
132+
}
133+
134+
public abstract class ClassWithBase
135+
{
136+
public virtual object Throws() => throw new InvalidOperationException();
137+
}
138+
111139
public interface ITestInterface1
112140
{
113141
Class1 Instance { get; }

AutofacContrib.NSubstitute.Tests/SubstituteForFixture.cs

+52-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using NSubstitute.Extensions;
44
using NUnit.Framework;
55
using System;
6-
using System.Collections.Generic;
7-
using System.Text;
86

97
namespace AutofacContrib.NSubstitute.Tests
108
{
@@ -92,9 +90,61 @@ public void FailsIfSubstituteTypeIsChanged2()
9290
Assert.Throws<InvalidOperationException>(() => builder.SubstituteFor<Test1>());
9391
}
9492

93+
[Test]
94+
public void PropertiesNotSetByDefault()
95+
{
96+
using var mock = AutoSubstitute.Configure()
97+
.Provide<IProperty, CustomProperty>(out _)
98+
.SubstituteFor<TestWithProperty>().Configured()
99+
.Build();
100+
101+
Assert.IsNull(mock.Resolve<TestWithProperty>().PropertySetter);
102+
Assert.That(mock.Resolve<TestWithProperty>().VirtualProperty, Is.NSubstituteMock);
103+
}
104+
105+
[Test]
106+
public void PropertiesSetIfRequested()
107+
{
108+
using var mock = AutoSubstitute.Configure()
109+
.Provide<IProperty, CustomProperty>(out var property)
110+
.SubstituteFor<TestWithProperty>()
111+
.InjectProperties()
112+
.Configured()
113+
.Build();
114+
115+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().PropertySetter);
116+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().VirtualProperty);
117+
}
118+
119+
[Test]
120+
public void PropertiesSetIfGloballyRequested()
121+
{
122+
using var mock = AutoSubstitute.Configure()
123+
.InjectProperties()
124+
.Provide<IProperty, CustomProperty>(out var property)
125+
.SubstituteFor<TestWithProperty>().Configured()
126+
.Build();
127+
128+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().PropertySetter);
129+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().VirtualProperty);
130+
}
131+
95132
public abstract class Test1
96133
{
97134
public virtual object Throws() => throw new InvalidOperationException();
98135
}
136+
137+
public abstract class TestWithProperty
138+
{
139+
public IProperty PropertySetter { get; set; }
140+
141+
public virtual IProperty VirtualProperty { get; }
142+
}
143+
144+
public interface IProperty
145+
{
146+
}
147+
148+
public class CustomProperty : IProperty { }
99149
}
100150
}

AutofacContrib.NSubstitute/AutoSubstituteBuilder.cs

+5
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ private SubstituteForBuilder<TService> CreateSubstituteForBuilder<TService>(Func
186186

187187
_substituteForRegistrations.Add(typeof(TService), builder);
188188

189+
if (_options.AutoInjectProperties)
190+
{
191+
builder.InjectProperties();
192+
}
193+
189194
return builder;
190195
}
191196

AutofacContrib.NSubstitute/AutoSubstituteOptions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace AutofacContrib.NSubstitute
66
{
77
public class AutoSubstituteOptions
88
{
9+
internal bool AutoInjectProperties { get; set; }
10+
911
/// <summary>
1012
/// Gets a collection of handlers that can be used to modify mocks after they are created.
1113
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using Autofac;
22
using Autofac.Core.Activators.Reflection;
3-
using NSubstitute.Core;
4-
using System;
53
using System.Reflection;
64

75
namespace AutofacContrib.NSubstitute
@@ -38,7 +36,8 @@ public static AutoSubstituteBuilder UnregisteredTypesUseInternalConstructor(this
3836
public static AutoSubstituteBuilder InjectProperties(this AutoSubstituteBuilder builder)
3937
=> builder.ConfigureOptions(options =>
4038
{
41-
options.MockHandlers.Add(new AutoPropertyInjectorMockHandler());
39+
options.AutoInjectProperties = true;
40+
options.MockHandlers.Add(AutoPropertyInjectorMockHandler.Instance);
4241
options.ConfigureAnyConcreteTypeRegistration.Add(r => r.PropertiesAutowired());
4342
});
4443

@@ -51,37 +50,5 @@ private NonPublicConstructorFinder()
5150
{
5251
}
5352
}
54-
55-
private class AutoPropertyInjectorMockHandler : MockHandler
56-
{
57-
protected internal override void OnMockCreated(object instance, Type type, IComponentContext context, ISubstitutionContext substitutionContext)
58-
{
59-
var router = substitutionContext.GetCallRouterFor(instance);
60-
61-
router.RegisterCustomCallHandlerFactory(_ => new AutoPropertyInjectorCallHandler(context));
62-
}
63-
64-
private class AutoPropertyInjectorCallHandler : ICallHandler
65-
{
66-
private readonly IComponentContext _context;
67-
68-
public AutoPropertyInjectorCallHandler(IComponentContext context)
69-
{
70-
_context = context;
71-
}
72-
73-
public RouteAction Handle(ICall call)
74-
{
75-
var property = call.GetMethodInfo().GetPropertyFromGetterCallOrNull();
76-
77-
if (property is null)
78-
{
79-
return RouteAction.Continue();
80-
}
81-
82-
return RouteAction.Return(_context.Resolve(call.GetReturnType()));
83-
}
84-
}
85-
}
8653
}
8754
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Autofac;
2+
using NSubstitute.Core;
3+
using System;
4+
5+
namespace AutofacContrib.NSubstitute
6+
{
7+
internal class AutoPropertyInjectorMockHandler : MockHandler
8+
{
9+
public static AutoPropertyInjectorMockHandler Instance { get; } = new AutoPropertyInjectorMockHandler();
10+
11+
private AutoPropertyInjectorMockHandler()
12+
{
13+
}
14+
15+
protected internal override void OnMockCreated(object instance, Type type, IComponentContext context, ISubstitutionContext substitutionContext)
16+
{
17+
var router = substitutionContext.GetCallRouterFor(instance);
18+
19+
router.RegisterCustomCallHandlerFactory(_ => new AutoPropertyInjectorCallHandler(context));
20+
}
21+
22+
private class AutoPropertyInjectorCallHandler : ICallHandler
23+
{
24+
private readonly IComponentContext _context;
25+
26+
public AutoPropertyInjectorCallHandler(IComponentContext context)
27+
{
28+
_context = context;
29+
}
30+
31+
public RouteAction Handle(ICall call)
32+
{
33+
var property = call.GetMethodInfo().GetPropertyFromGetterCallOrNull();
34+
35+
if (property is null)
36+
{
37+
return RouteAction.Continue();
38+
}
39+
40+
return RouteAction.Return(_context.Resolve(call.GetReturnType()));
41+
}
42+
}
43+
}
44+
}

AutofacContrib.NSubstitute/SubstituteForBuilder.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Autofac;
22
using Autofac.Builder;
3+
using NSubstitute.Core;
34
using System;
45

56
namespace AutofacContrib.NSubstitute
@@ -29,6 +30,8 @@ internal SubstituteForBuilder(
2930
/// </summary>
3031
internal bool IsSubstituteFor { get; }
3132

33+
internal ISubstitutionContext Context => SubstitutionContext.Current;
34+
3235
/// <summary>
3336
/// Allows for configuration of the service.
3437
/// </summary>
@@ -46,7 +49,7 @@ public AutoSubstituteBuilder Configure(Action<TService, IComponentContext> actio
4649
{
4750
_registration.OnActivated(args =>
4851
{
49-
action(args.Instance, args.Context);
52+
action(args.Instance, args.Context.Resolve<IComponentContext>());
5053
});
5154

5255
return _builder;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using NSubstitute.Core;
2+
using Autofac;
3+
4+
namespace AutofacContrib.NSubstitute
5+
{
6+
public static class SubstituteForBuilderExtensions
7+
{
8+
/// <summary>
9+
/// Configures the auto-generated instance of <typeparamref name="T"/> to not call the base method for any of the methods.
10+
/// </summary>
11+
/// <typeparam name="T"></typeparam>
12+
/// <param name="builder"></param>
13+
/// <returns></returns>
14+
public static SubstituteForBuilder<T> DoNotCallBase<T>(this SubstituteForBuilder<T> builder)
15+
where T : class
16+
{
17+
builder.Configure(t =>
18+
{
19+
var router = SubstitutionContext.Current.GetCallRouterFor(t);
20+
21+
router.CallBaseByDefault = false;
22+
});
23+
24+
return builder;
25+
}
26+
27+
public static SubstituteForBuilder<T> InjectProperties<T>(this SubstituteForBuilder<T> builder)
28+
where T : class
29+
{
30+
builder.Configure((t, ctx) =>
31+
{
32+
ctx.InjectUnsetProperties(t);
33+
AutoPropertyInjectorMockHandler.Instance.OnMockCreated(t, typeof(T), ctx, builder.Context);
34+
});
35+
36+
return builder;
37+
}
38+
}
39+
}

README.md

+53
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,59 @@ public void BaseCalledOnSubstituteForPartsOf()
208208
}
209209
```
210210

211+
There is a helper method to ensure base calls are not routed by default for any method on partial substitutes if desired:
212+
213+
```c#
214+
public abstract class Test1
215+
{
216+
public virtual object Throws() => throw new InvalidOperationException();
217+
}
218+
219+
[Test]
220+
public void BaseCalledOnSubstituteForPartsOf()
221+
{
222+
using var mock = AutoSubstitute.Configure()
223+
.SubstituteForPartsOf<Test1>().DoNotCallBase().Configured()
224+
.Build();
225+
226+
var test1 = mock.Resolve<Test1>();
227+
228+
Assert.IsNull(test1.Throws());
229+
}
230+
```
231+
232+
You can also enforce all properties to be automatically injected on this substitute:
233+
234+
```c#
235+
[Test]
236+
public void PropertiesSetIfRequested()
237+
{
238+
using var mock = AutoSubstitute.Configure()
239+
.Provide<IProperty, CustomProperty>(out var property)
240+
.SubstituteFor<TestWithProperty>()
241+
.InjectProperties()
242+
.Configured()
243+
.Build();
244+
245+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().PropertySetter);
246+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().VirtualProperty);
247+
}
248+
249+
[Test]
250+
public void PropertiesSetIfGloballyRequested()
251+
{
252+
using var mock = AutoSubstitute.Configure()
253+
.InjectProperties()
254+
.Provide<IProperty, CustomProperty>(out var property)
255+
.SubstituteFor<TestWithProperty>().Configured()
256+
.Build();
257+
258+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().PropertySetter);
259+
Assert.AreEqual(property.Value, mock.Resolve<TestWithProperty>().VirtualProperty);
260+
}
261+
262+
```
263+
211264
Similarly, you can resolve a concrete type from the AutoSubstitute container and register that with the underlying container using the `ResolveAndSubstituteFor` method:
212265

213266
```c#

0 commit comments

Comments
 (0)