Skip to content

Commit 3584a43

Browse files
authored
Add support for Substitute.ForPartsOf to AutoSubstituteBuilder (#43)
Fixes #19
1 parent 5d2f7b4 commit 3584a43

File tree

7 files changed

+202
-68
lines changed

7 files changed

+202
-68
lines changed

AutofacContrib.NSubstitute.Tests/ExampleFixture.cs

-37
Original file line numberDiff line numberDiff line change
@@ -166,43 +166,6 @@ public void Example_test_with_concrete_object_provided()
166166
Assert.That(result, Is.EqualTo(val1 + val2));
167167
}
168168

169-
[Test]
170-
public void Example_test_with_substitute_for_concrete()
171-
{
172-
const int val1 = 3;
173-
const int val2 = 2;
174-
const int val3 = 10;
175-
176-
using var utoSubstitute = AutoSubstitute.Configure()
177-
.SubstituteFor<ConcreteClass>(val2).Configure(c => c.Add(Arg.Any<int>()).Returns(val3))
178-
.Build();
179-
180-
utoSubstitute.Resolve<IDependency2>().SomeOtherMethod().Returns(val1);
181-
182-
var result = utoSubstitute.Resolve<MyClassWithConcreteDependency>().AMethod();
183-
184-
Assert.That(result, Is.EqualTo(val3));
185-
}
186-
187-
[Test]
188-
public void SubstituteForConfigureWithContext()
189-
{
190-
const int val = 2;
191-
192-
using var utoSubstitute = AutoSubstitute.Configure()
193-
.SubstituteFor<ConcreteClass>(val).Configured()
194-
.SubstituteFor<ConcreteClassWithObject>().Configure((s, ctx) =>
195-
{
196-
s.Configure().GetResult().Returns(ctx.Resolve<ConcreteClass>());
197-
})
198-
.Build()
199-
.Container;
200-
201-
var result = utoSubstitute.Resolve<ConcreteClassWithObject>().GetResult();
202-
203-
Assert.AreSame(result, utoSubstitute.Resolve<ConcreteClass>());
204-
}
205-
206169
[Test]
207170
public void Example_test_with_substitute_for_concrete_resolved_from_autofac()
208171
{

AutofacContrib.NSubstitute.Tests/NSubstituteExtensions.cs

-13
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using NSubstitute.Core;
2+
using NSubstitute.Exceptions;
3+
using NUnit.Framework.Constraints;
4+
5+
namespace AutofacContrib.NSubstitute.Tests
6+
{
7+
internal class NSubstituteConstraint : Constraint
8+
{
9+
public NSubstituteConstraint()
10+
{
11+
Description = "Object expected to be NSubstitute mock";
12+
}
13+
14+
public override ConstraintResult ApplyTo<TActual>(TActual actual)
15+
{
16+
try
17+
{
18+
var router = SubstitutionContext.Current.GetCallRouterFor(actual);
19+
20+
return new ConstraintResult(this, actual, router != null);
21+
}
22+
catch (NotASubstituteException)
23+
{
24+
return new ConstraintResult(this, actual, false);
25+
}
26+
}
27+
}
28+
29+
internal class Is : NUnit.Framework.Is
30+
{
31+
public static NSubstituteConstraint NSubstituteMock { get; } = new NSubstituteConstraint();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using Autofac;
2+
using NSubstitute;
3+
using NSubstitute.Extensions;
4+
using NUnit.Framework;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Text;
8+
9+
namespace AutofacContrib.NSubstitute.Tests
10+
{
11+
[TestFixture]
12+
public class SubstituteForFixture
13+
{
14+
[Test]
15+
public void Example_test_with_substitute_for_concrete()
16+
{
17+
const int val1 = 3;
18+
const int val2 = 2;
19+
const int val3 = 10;
20+
21+
using var utoSubstitute = AutoSubstitute.Configure()
22+
.SubstituteFor<ConcreteClass>(val2).Configure(c => c.Add(Arg.Any<int>()).Returns(val3))
23+
.Build();
24+
25+
utoSubstitute.Resolve<IDependency2>().SomeOtherMethod().Returns(val1);
26+
27+
var result = utoSubstitute.Resolve<MyClassWithConcreteDependency>().AMethod();
28+
29+
Assert.That(result, Is.EqualTo(val3));
30+
}
31+
32+
[Test]
33+
public void SubstituteForConfigureWithContext()
34+
{
35+
const int val = 2;
36+
37+
using var utoSubstitute = AutoSubstitute.Configure()
38+
.SubstituteFor<ConcreteClass>(val).Configured()
39+
.SubstituteFor<ConcreteClassWithObject>().Configure((s, ctx) =>
40+
{
41+
s.Configure().GetResult().Returns(ctx.Resolve<ConcreteClass>());
42+
})
43+
.Build()
44+
.Container;
45+
46+
var result = utoSubstitute.Resolve<ConcreteClassWithObject>().GetResult();
47+
48+
Assert.AreSame(result, utoSubstitute.Resolve<ConcreteClass>());
49+
}
50+
51+
[Test]
52+
public void BaseCalledOnSubstituteForPartsOf()
53+
{
54+
using var mock = AutoSubstitute.Configure()
55+
.SubstituteForPartsOf<Test1>().Configured()
56+
.Build();
57+
58+
var test1 = mock.Resolve<Test1>();
59+
60+
Assert.That(test1, Is.NSubstituteMock);
61+
Assert.Throws<InvalidOperationException>(() => test1.Throws());
62+
}
63+
64+
[Test]
65+
public void BaseNotCalledOnSubstituteFor()
66+
{
67+
using var mock = AutoSubstitute.Configure()
68+
.SubstituteFor<Test1>().Configured()
69+
.Build();
70+
71+
var test1 = mock.Resolve<Test1>();
72+
73+
Assert.That(test1, Is.NSubstituteMock);
74+
Assert.Null(test1.Throws());
75+
}
76+
77+
[Test]
78+
public void FailsIfSubstituteTypeIsChanged()
79+
{
80+
var builder = AutoSubstitute.Configure()
81+
.SubstituteFor<Test1>().Configured();
82+
83+
Assert.Throws<InvalidOperationException>(() => builder.SubstituteForPartsOf<Test1>());
84+
}
85+
86+
[Test]
87+
public void FailsIfSubstituteTypeIsChanged2()
88+
{
89+
var builder = AutoSubstitute.Configure()
90+
.SubstituteForPartsOf<Test1>().Configured();
91+
92+
Assert.Throws<InvalidOperationException>(() => builder.SubstituteFor<Test1>());
93+
}
94+
95+
public abstract class Test1
96+
{
97+
public virtual object Throws() => throw new InvalidOperationException();
98+
}
99+
}
100+
}

AutofacContrib.NSubstitute/AutoSubstituteBuilder.cs

+37-16
Original file line numberDiff line numberDiff line change
@@ -126,30 +126,26 @@ public AutoSubstituteBuilder Provide<TService>(TService instance, object service
126126
}
127127

128128
/// <summary>
129-
/// Registers to the container and returns a substitute for a given concrete class given the explicit constructor parameters.
129+
/// Registers a substitute to the container a given concrete class given the explicit constructor parameters using <see cref="Substitute.For{TService}(object[])"/>.
130130
/// This is used for concrete classes where NSubstitutes won't be created by default by the container when using Resolve.
131-
/// For advanced uses consider using directly <see cref="Substitute.For{TService}"/> and then calling <see cref="Provide{TService}(TService)"/> so that type is used on dependencies for other Resolved types.
132131
/// </summary>
133132
/// <typeparam name="TService">The type to register and return a substitute for</typeparam>
134133
/// <param name="parameters">Optional constructor parameters</param>
135134
/// <returns>An instance to help configure the substitution.</returns>
136135
public SubstituteForBuilder<TService> SubstituteFor<TService>(params object[] parameters)
137136
where TService : class
138-
{
139-
if (_substituteForRegistrations.TryGetValue(typeof(TService), out var result))
140-
{
141-
return (SubstituteForBuilder<TService>)result;
142-
}
143-
144-
var registration = _builder.Register(_ => Substitute.For<TService>(parameters))
145-
.As<TService>()
146-
.InstancePerLifetimeScope();
147-
var builder = new SubstituteForBuilder<TService>(this, registration);
148-
149-
_substituteForRegistrations.Add(typeof(TService), builder);
137+
=> CreateSubstituteForBuilder<TService>(() => Substitute.For<TService>(parameters), true);
150138

151-
return builder;
152-
}
139+
/// <summary>
140+
/// Registers a substitute to the container a given concrete class given the explicit constructor parameters using <see cref="Substitute.ForPartsOf{TService}(object[])"/>.
141+
/// This is used for concrete classes where NSubstitutes won't be created by default by the container when using Resolve.
142+
/// </summary>
143+
/// <typeparam name="TService">The type to register and return a substitute for</typeparam>
144+
/// <param name="parameters">Optional constructor parameters</param>
145+
/// <returns>An instance to help configure the substitution.</returns>
146+
public SubstituteForBuilder<TService> SubstituteForPartsOf<TService>(params object[] parameters)
147+
where TService : class
148+
=> CreateSubstituteForBuilder(() => Substitute.ForPartsOf<TService>(parameters), false);
153149

154150
/// <summary>
155151
/// Registers to the container and returns a substitute for a given concrete class using autofac to resolve the constructor parameters.
@@ -168,6 +164,31 @@ public AutoSubstituteBuilder ResolveAndSubstituteFor<TService>(params Parameter[
168164
return this;
169165
}
170166

167+
private SubstituteForBuilder<TService> CreateSubstituteForBuilder<TService>(Func<TService> factory, bool isSubstituteFor)
168+
where TService : class
169+
{
170+
if (_substituteForRegistrations.TryGetValue(typeof(TService), out var result))
171+
{
172+
var previousBuilder = (SubstituteForBuilder<TService>)result;
173+
174+
if (previousBuilder.IsSubstituteFor != isSubstituteFor)
175+
{
176+
throw new InvalidOperationException("Cannot change a service registration between SubstituteFor and SubstituteForPartsOf");
177+
}
178+
179+
return previousBuilder;
180+
}
181+
182+
var registration = _builder.Register(_ => factory())
183+
.As<TService>()
184+
.InstancePerLifetimeScope();
185+
var builder = new SubstituteForBuilder<TService>(this, registration, isSubstituteFor);
186+
187+
_substituteForRegistrations.Add(typeof(TService), builder);
188+
189+
return builder;
190+
}
191+
171192
internal IProvidedValue<TService> CreateProvidedValue<TService>(Func<IContainer, TService> factory)
172193
{
173194
var value = new ProvidedValue<TService>(factory);

AutofacContrib.NSubstitute/SubstituteForBuilder.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@ public class SubstituteForBuilder<TService>
1414
private readonly AutoSubstituteBuilder _builder;
1515
private readonly IRegistrationBuilder<TService, SimpleActivatorData, SingleRegistrationStyle> _registration;
1616

17-
internal SubstituteForBuilder(AutoSubstituteBuilder builder, IRegistrationBuilder<TService, SimpleActivatorData, SingleRegistrationStyle> registration)
17+
internal SubstituteForBuilder(
18+
AutoSubstituteBuilder builder,
19+
IRegistrationBuilder<TService, SimpleActivatorData, SingleRegistrationStyle> registration,
20+
bool isSubstituteFor)
1821
{
1922
_builder = builder;
2023
_registration = registration;
24+
IsSubstituteFor = isSubstituteFor;
2125
}
2226

27+
/// <summary>
28+
/// Used to identify if a builder was created by <see cref="AutoSubstituteBuilder.SubstituteFor{TService}(object[])"/> or <see cref="AutoSubstituteBuilder.SubstituteForPartsOf{TService}(object[])"/>.
29+
/// </summary>
30+
internal bool IsSubstituteFor { get; }
31+
2332
/// <summary>
2433
/// Allows for configuration of the service.
2534
/// </summary>

README.md

+22-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,28 @@ public void SubstituteForConfigureWithContext()
187187
}
188188
```
189189

190-
Similarly, you can resolve a concrete type from the autosubstitute container and register that with the underlying container using the `ResolveAndSubstituteFor` method:
190+
If you would rather register it using NSubstitute's `SubstituteForPartsOf` method, you may use the following:
191+
192+
```c#
193+
public abstract class Test1
194+
{
195+
public virtual object Throws() => throw new InvalidOperationException();
196+
}
197+
198+
[Test]
199+
public void BaseCalledOnSubstituteForPartsOf()
200+
{
201+
using var mock = AutoSubstitute.Configure()
202+
.SubstituteForPartsOf<Test1>().Configured()
203+
.Build();
204+
205+
var test1 = mock.Resolve<Test1>();
206+
207+
Assert.Throws<InvalidOperationException>(() => test1.Throws());
208+
}
209+
```
210+
211+
Similarly, you can resolve a concrete type from the AutoSubstitute container and register that with the underlying container using the `ResolveAndSubstituteFor` method:
191212

192213
```c#
193214
public class ConcreteClassWithDependency

0 commit comments

Comments
 (0)