-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Enable call forwarding and substitution for non virtual meth…
…ods 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.
- Loading branch information
1 parent
e58685a
commit 05cb82c
Showing
5 changed files
with
147 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
|
||
namespace NSubstitute.Exceptions | ||
Check failure on line 3 in src/NSubstitute/Exceptions/TypeForwardingException.cs GitHub Actions / format-verify
Check failure on line 3 in src/NSubstitute/Exceptions/TypeForwardingException.cs GitHub Actions / format-verify
|
||
{ | ||
public abstract class TypeForwardingException : SubstituteException | ||
{ | ||
protected TypeForwardingException(string message) : base(message) { } | ||
} | ||
|
||
public sealed class CanNotForwardCallsToClassNotImplementingInterfaceException : TypeForwardingException | ||
{ | ||
public CanNotForwardCallsToClassNotImplementingInterfaceException(Type type) : base(DescribeProblem(type)) { } | ||
private static string DescribeProblem(Type type) | ||
{ | ||
return string.Format("The provided class '{0}' doesn't implement all requested interfaces. ", type.Name); | ||
} | ||
} | ||
|
||
public sealed class CanNotForwardCallsToAbstractClassException : TypeForwardingException | ||
{ | ||
public CanNotForwardCallsToAbstractClassException(Type type) : base(DescribeProblem(type)) { } | ||
private static string DescribeProblem(Type type) | ||
{ | ||
return string.Format("The provided class '{0}' is abstract. ", type.Name); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
using System; | ||
|
||
using NSubstitute.Core; | ||
using NSubstitute.Exceptions; | ||
using NSubstitute.Extensions; | ||
|
||
using NUnit.Framework; | ||
|
||
namespace NSubstitute.Acceptance.Specs | ||
Check warning on line 9 in tests/NSubstitute.Acceptance.Specs/TypeForwarding.cs GitHub Actions / format-verify
Check warning on line 9 in tests/NSubstitute.Acceptance.Specs/TypeForwarding.cs GitHub Actions / format-verify
Check warning on line 9 in tests/NSubstitute.Acceptance.Specs/TypeForwarding.cs GitHub Actions / format-verify
|
||
{ | ||
public class TypeForwarding | ||
{ | ||
[Test] | ||
public void UseImplementedNonVirtualMethod() | ||
{ | ||
var testAbstractClass = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>(); | ||
Assert.That(testAbstractClass.MethodReturnsSameInt(1), Is.EqualTo(1)); | ||
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1)); | ||
testAbstractClass.Received().MethodReturnsSameInt(1); | ||
Assert.That(testAbstractClass.CalledTimes, Is.EqualTo(1)); | ||
} | ||
|
||
[Test] | ||
public void UseSubstitutedNonVirtualMethod() | ||
{ | ||
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>(); | ||
testInterface.Configure().MethodReturnsSameInt(1).Returns(2); | ||
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2)); | ||
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(3)); | ||
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default); | ||
Assert.That(testInterface.CalledTimes, Is.EqualTo(1)); | ||
} | ||
|
||
[Test] | ||
public void UseSubstitutedNonVirtualMethodHonorsDoNotCallBase() | ||
{ | ||
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>(); | ||
testInterface.Configure().MethodReturnsSameInt(1).Returns(2); | ||
testInterface.WhenForAnyArgs(x => x.MethodReturnsSameInt(default)).DoNotCallBase(); | ||
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(2)); | ||
Assert.That(testInterface.MethodReturnsSameInt(3), Is.EqualTo(0)); | ||
testInterface.ReceivedWithAnyArgs(2).MethodReturnsSameInt(default); | ||
Assert.That(testInterface.CalledTimes, Is.EqualTo(0)); | ||
} | ||
|
||
[Test] | ||
public void PartialSubstituteCallsConstructorWithParameters() | ||
{ | ||
var testInterface = Substitute.ForTypeForwardingTo<ITestInterface, TestSealedNonVirtualClass>(50); | ||
Assert.That(testInterface.MethodReturnsSameInt(1), Is.EqualTo(1)); | ||
Assert.That(testInterface.CalledTimes, Is.EqualTo(51)); | ||
} | ||
|
||
[Test] | ||
public void PartialSubstituteFailsIfClassDoesntImplementInterface() | ||
{ | ||
Assert.Throws<CanNotForwardCallsToClassNotImplementingInterfaceException>( | ||
() => Substitute.ForTypeForwardingTo<ITestInterface, TestRandomConcreteClass>()); | ||
} | ||
|
||
[Test] | ||
public void PartialSubstituteFailsIfClassIsAbstract() | ||
{ | ||
Assert.Throws<CanNotForwardCallsToAbstractClassException>( | ||
() => Substitute.ForTypeForwardingTo<ITestInterface, TestAbstractClassWithInterface>(), "The provided class is abstract."); | ||
} | ||
|
||
public interface ITestInterface | ||
{ | ||
public int CalledTimes { get; set; } | ||
|
||
void VoidTestMethod(); | ||
int TestMethodReturnsInt(); | ||
int MethodReturnsSameInt(int i); | ||
} | ||
|
||
public sealed class TestSealedNonVirtualClass : ITestInterface | ||
{ | ||
public TestSealedNonVirtualClass(int initialCounter) => CalledTimes = initialCounter; | ||
public TestSealedNonVirtualClass() { } | ||
|
||
public int CalledTimes { get; set; } | ||
|
||
public int TestMethodReturnsInt() => throw new NotImplementedException(); | ||
|
||
public void VoidTestMethod() => throw new NotImplementedException(); | ||
public int MethodReturnsSameInt(int i) | ||
{ | ||
CalledTimes++; | ||
return i; | ||
} | ||
} | ||
|
||
public abstract class TestAbstractClassWithInterface : ITestInterface | ||
{ | ||
public int CalledTimes { get; set; } | ||
|
||
public abstract int MethodReturnsSameInt(int i); | ||
|
||
public abstract int TestMethodReturnsInt(); | ||
|
||
public abstract void VoidTestMethod(); | ||
} | ||
|
||
public class TestRandomConcreteClass { } | ||
|
||
public abstract class TestAbstractClass { } | ||
} | ||
} |