Skip to content

Commit 05cb82c

Browse files
committed
Feature: Enable call forwarding and substitution for non virtual methods or sealed classes implementing an interface.
How to use: var substitute = Substitute.ForTypeForwardingTo <ISomeInterface,SomeImplementation>(argsList); In this case, it doesn't matter if methods are virtual or not; it will intercept all calls since we will be working with an interface all the time. For Limitations: Overriding virtual methods effectively replaces its implementation both for internal and external calls. With this implementation Nsubstitute will only intercept calls made by client classes using the interface. Calls made from inside the object itself to it's own method, will hit the actual implementation.
1 parent e58685a commit 05cb82c

File tree

5 files changed

+147
-93
lines changed

5 files changed

+147
-93
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
3+
namespace NSubstitute.Exceptions
4+
{
5+
public abstract class TypeForwardingException : SubstituteException
6+
{
7+
protected TypeForwardingException(string message) : base(message) { }
8+
}
9+
10+
public sealed class CanNotForwardCallsToClassNotImplementingInterfaceException : TypeForwardingException
11+
{
12+
public CanNotForwardCallsToClassNotImplementingInterfaceException(Type type) : base(DescribeProblem(type)) { }
13+
private static string DescribeProblem(Type type)
14+
{
15+
return string.Format("The provided class '{0}' doesn't implement all requested interfaces. ", type.Name);
16+
}
17+
}
18+
19+
public sealed class CanNotForwardCallsToAbstractClassException : TypeForwardingException
20+
{
21+
public CanNotForwardCallsToAbstractClassException(Type type) : base(DescribeProblem(type)) { }
22+
private static string DescribeProblem(Type type)
23+
{
24+
return string.Format("The provided class '{0}' is abstract. ", type.Name);
25+
}
26+
}
27+
}

src/NSubstitute/Proxies/CastleDynamicProxy/CastleDynamicProxyFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,15 @@ private static void VerifyClassImplementsAllInterfaces(Type classType, IEnumerab
168168
{
169169
if (!additionalInterfaces.All(x => x.GetTypeInfo().IsAssignableFrom(classType.GetTypeInfo())))
170170
{
171-
throw new SubstituteException("The provided class doesn't implement all requested interfaces.");
171+
throw new CanNotForwardCallsToClassNotImplementingInterfaceException(classType);
172172
}
173173
}
174174

175175
private static void VerifyClassIsNotAbstract(Type classType)
176176
{
177177
if (classType.GetTypeInfo().IsAbstract)
178178
{
179-
throw new SubstituteException("The provided class is abstract.");
179+
throw new CanNotForwardCallsToAbstractClassException(classType);
180180
}
181181
}
182182

src/NSubstitute/Substitute.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,18 @@ public static T ForPartsOf<T>(params object[] constructorArguments)
9191
return (T)substituteFactory.CreatePartial(new[] { typeof(T) }, constructorArguments);
9292
}
9393

94-
public static T ForPartsOf<T,TClass>(params object[] constructorArguments)
94+
public static T ForTypeForwardingTo<T,TClass>(params object[] constructorArguments)
9595
where T : class
9696
{
9797
var substituteFactory = SubstitutionContext.Current.SubstituteFactory;
9898
return (T)substituteFactory.CreatePartial(new[] { typeof(T), typeof(TClass) }, constructorArguments);
9999
}
100+
101+
//public static T ForTypeForwardingTo<T, T2, T3>(params object[] constructorArguments)
102+
// where T : class
103+
//{
104+
// var substituteFactory = SubstitutionContext.Current.SubstituteFactory;
105+
// return (T)substituteFactory.CreatePartial(new[] { typeof(T), typeof(TClass) }, constructorArguments);
106+
//}
107+
100108
}

tests/NSubstitute.Acceptance.Specs/PartialSubs.cs

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11

2-
32
using NSubstitute.Core;
43
using NSubstitute.Exceptions;
54
using NSubstitute.Extensions;
6-
75
using NUnit.Framework;
86
using NUnit.Framework.Legacy;
97

@@ -90,63 +88,6 @@ public void UseImplementedVirtualMethod()
9088
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1));
9189
}
9290

93-
94-
[Test]
95-
public void UseImplementedNonVirtualMethod()
96-
{
97-
var testAbstractClass = Substitute.ForPartsOf<ITestInterface, TestNonVirtualClass>();
98-
Assert.That(testAbstractClass.MethodReturnsSameInt(1), Is.EqualTo(1));
99-
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1));
100-
testAbstractClass.Received().MethodReturnsSameInt(1);
101-
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1));
102-
}
103-
104-
[Test]
105-
public void UseSubstitutedNonVirtualMethod()
106-
{
107-
var testInterface = Substitute.ForPartsOf<ITestInterface, TestNonVirtualClass>();
108-
testInterface.Configure().MethodReturnsSameInt(1).Returns(2);
109-
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2));
110-
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(3));
111-
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default);
112-
Assert.That(testInterface.CalledTimes, Is.EqualTo(1));
113-
}
114-
115-
[Test]
116-
public void UseSubstitutedNonVirtualMethodHonorsDoNotCallBase()
117-
{
118-
var testInterface = Substitute.ForPartsOf<ITestInterface, TestNonVirtualClass>();
119-
testInterface.Configure().MethodReturnsSameInt(1).Returns(2);
120-
testInterface.WhenForAnyArgs(x => x.MethodReturnsSameInt(default)).DoNotCallBase();
121-
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2));
122-
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(0));
123-
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default);
124-
Assert.That(testInterface.CalledTimes, Is.EqualTo(0));
125-
}
126-
127-
[Test]
128-
public void PartialSubstituteCallsConstructorWithParameters()
129-
{
130-
var testInterface = Substitute.ForPartsOf<ITestInterface, TestNonVirtualClass>(50);
131-
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(1));
132-
Assert.That(testInterface.CalledTimes, Is.EqualTo(51));
133-
}
134-
135-
[Test]
136-
public void PartialSubstituteFailsIfClassDoesntImplementInterface()
137-
{
138-
Assert.Throws<SubstituteException>(
139-
() => Substitute.ForPartsOf<ITestInterface, TestAbstractClass>());
140-
}
141-
142-
143-
[Test]
144-
public void PartialSubstituteFailsIfClassIsAbstract()
145-
{
146-
Assert.Throws<SubstituteException>(
147-
() => Substitute.ForPartsOf<ITestInterface, TestAbstractClassWithInterface>(), "The provided class is abstract.");
148-
}
149-
15091
[Test]
15192
public void ReturnDefaultForUnimplementedAbstractMethod()
15293
{
@@ -367,39 +308,8 @@ public void ShouldThrowExceptionIfConfigureGlobalCallBaseForDelegateProxy()
367308

368309
public interface ITestInterface
369310
{
370-
public int CalledTimes { get; set; }
371-
372311
void VoidTestMethod();
373312
int TestMethodReturnsInt();
374-
int MethodReturnsSameInt(int i);
375-
}
376-
377-
public class TestNonVirtualClass : ITestInterface
378-
{
379-
public TestNonVirtualClass() { }
380-
public TestNonVirtualClass(int initialCounter) => CalledTimes = initialCounter;
381-
382-
public int CalledTimes { get; set; }
383-
384-
public int TestMethodReturnsInt() => throw new NotImplementedException();
385-
386-
public void VoidTestMethod() => throw new NotImplementedException();
387-
public int MethodReturnsSameInt(int i)
388-
{
389-
CalledTimes++;
390-
return i;
391-
}
392-
}
393-
394-
public abstract class TestAbstractClassWithInterface : ITestInterface
395-
{
396-
public int CalledTimes { get; set; }
397-
398-
public abstract int MethodReturnsSameInt(int i);
399-
400-
public abstract int TestMethodReturnsInt();
401-
402-
public abstract void VoidTestMethod();
403313
}
404314

405315
public abstract class TestAbstractClass
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
3+
using NSubstitute.Core;
4+
using NSubstitute.Exceptions;
5+
using NSubstitute.Extensions;
6+
7+
using NUnit.Framework;
8+
9+
namespace NSubstitute.Acceptance.Specs
10+
{
11+
public class TypeForwarding
12+
{
13+
[Test]
14+
public void UseImplementedNonVirtualMethod()
15+
{
16+
var testAbstractClass = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>();
17+
Assert.That(testAbstractClass.MethodReturnsSameInt(1), Is.EqualTo(1));
18+
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1));
19+
testAbstractClass.Received().MethodReturnsSameInt(1);
20+
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1));
21+
}
22+
23+
[Test]
24+
public void UseSubstitutedNonVirtualMethod()
25+
{
26+
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>();
27+
testInterface.Configure().MethodReturnsSameInt(1).Returns(2);
28+
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2));
29+
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(3));
30+
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default);
31+
Assert.That(testInterface.CalledTimes, Is.EqualTo(1));
32+
}
33+
34+
[Test]
35+
public void UseSubstitutedNonVirtualMethodHonorsDoNotCallBase()
36+
{
37+
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>();
38+
testInterface.Configure().MethodReturnsSameInt(1).Returns(2);
39+
testInterface.WhenForAnyArgs(x => x.MethodReturnsSameInt(default)).DoNotCallBase();
40+
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2));
41+
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(0));
42+
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default);
43+
Assert.That(testInterface.CalledTimes, Is.EqualTo(0));
44+
}
45+
46+
[Test]
47+
public void PartialSubstituteCallsConstructorWithParameters()
48+
{
49+
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>(50);
50+
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(1));
51+
Assert.That(testInterface.CalledTimes, Is.EqualTo(51));
52+
}
53+
54+
[Test]
55+
public void PartialSubstituteFailsIfClassDoesntImplementInterface()
56+
{
57+
Assert.Throws<CanNotForwardCallsToClassNotImplementingInterfaceException>(
58+
() => Substitute.ForTypeForwardingTo<ITestInterface, TestRandomConcreteClass>());
59+
}
60+
61+
[Test]
62+
public void PartialSubstituteFailsIfClassIsAbstract()
63+
{
64+
Assert.Throws<CanNotForwardCallsToAbstractClassException>(
65+
() => Substitute.ForTypeForwardingTo<ITestInterface, TestAbstractClassWithInterface>(), "The provided class is abstract.");
66+
}
67+
68+
public interface ITestInterface
69+
{
70+
public int CalledTimes { get; set; }
71+
72+
void VoidTestMethod();
73+
int TestMethodReturnsInt();
74+
int MethodReturnsSameInt(int i);
75+
}
76+
77+
public sealed class TestSealedNonVirtualClass : ITestInterface
78+
{
79+
public TestSealedNonVirtualClass(int initialCounter) => CalledTimes = initialCounter;
80+
public TestSealedNonVirtualClass() { }
81+
82+
public int CalledTimes { get; set; }
83+
84+
public int TestMethodReturnsInt() => throw new NotImplementedException();
85+
86+
public void VoidTestMethod() => throw new NotImplementedException();
87+
public int MethodReturnsSameInt(int i)
88+
{
89+
CalledTimes++;
90+
return i;
91+
}
92+
}
93+
94+
public abstract class TestAbstractClassWithInterface : ITestInterface
95+
{
96+
public int CalledTimes { get; set; }
97+
98+
public abstract int MethodReturnsSameInt(int i);
99+
100+
public abstract int TestMethodReturnsInt();
101+
102+
public abstract void VoidTestMethod();
103+
}
104+
105+
public class TestRandomConcreteClass { }
106+
107+
public abstract class TestAbstractClass { }
108+
}
109+
}

0 commit comments

Comments
 (0)