From 3cabac1f53e9a4ae2a6648e2bc5aae3b4667b2b7 Mon Sep 17 00:00:00 2001 From: tpodolak Date: Sun, 6 Dec 2020 00:30:02 +0100 Subject: [PATCH] [GH-153] - reentrant tests --- .../ReEntrantSetupCodeFixProvider.cs | 64 ++++------------ .../AbstractReEntrantSetupCodeFixProvider.cs | 34 +++++---- .../Extensions/IOperationExtensions.cs | 20 ----- .../Extensions/SyntaxGeneratorExtensions.cs | 16 +++- .../ReEntrantSetupCodeFixProvider.cs | 73 ++++--------------- .../ReturnsAsExtensionMethodTests.cs | 2 + .../ReturnsAsOrdinaryMethodTests.cs | 4 + .../ReturnsAsOrdinaryMethodTests.cs | 4 + 8 files changed, 72 insertions(+), 145 deletions(-) diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs index 3ae0a0a8..ec05672b 100644 --- a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Simplification; using NSubstitute.Analyzers.CSharp.Extensions; using NSubstitute.Analyzers.Shared.CodeFixProviders; +using NSubstitute.Analyzers.Shared.Extensions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace NSubstitute.Analyzers.CSharp.CodeFixProviders @@ -31,41 +32,17 @@ protected override IEnumerable GetParameterExpressionsFromArrayArgum protected override ArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode( SyntaxGenerator syntaxGenerator, ITypeSymbol typeSymbol, - ArgumentSyntax argumentSyntaxNode) + ArgumentSyntax argumentSyntaxNode, + IEnumerable initializers) { - var expression = argumentSyntaxNode.Expression; - ArrayCreationExpressionSyntax resultArrayCreationExpressionSyntax; + var arrayCreationExpression = CreateArrayCreationExpression(syntaxGenerator, typeSymbol, initializers); - switch (expression) - { - case ArrayCreationExpressionSyntax arrayCreationExpressionSyntax: - resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( - syntaxGenerator, - typeSymbol, - arrayCreationExpressionSyntax.Initializer); - break; - case ImplicitArrayCreationExpressionSyntax implicitArrayCreationExpressionSyntax: - resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( - syntaxGenerator, - typeSymbol, - implicitArrayCreationExpressionSyntax.Initializer); - break; - default: - throw new ArgumentException($"{argumentSyntaxNode.Kind()} is not recognized as array initialization", nameof(argumentSyntaxNode)); - } - - return argumentSyntaxNode.WithExpression(resultArrayCreationExpressionSyntax); + return argumentSyntaxNode.WithExpression(arrayCreationExpression); } - protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) - { - return argumentSyntax.Arguments; - } + protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) => argumentSyntax.Arguments; - protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) - { - return argumentSyntax.Expression; - } + protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) => argumentSyntax.Expression; private static SimpleLambdaExpressionSyntax CreateSimpleLambdaExpressionNode(SyntaxNode content) { @@ -77,43 +54,28 @@ private static SimpleLambdaExpressionSyntax CreateSimpleLambdaExpressionNode(Syn private static ArrayCreationExpressionSyntax CreateArrayCreationExpression( SyntaxGenerator syntaxGenerator, ITypeSymbol typeSymbol, - InitializerExpressionSyntax initializerExpressionSyntax) + IEnumerable initializers) { var arrayType = CreateArrayTypeNode(syntaxGenerator, typeSymbol); - var syntaxes = CreateSimpleLambdaExpressions(initializerExpressionSyntax); + var syntaxes = CreateSimpleLambdaExpressions(initializers); var initializer = InitializerExpression(SyntaxKind.ArrayInitializerExpression, syntaxes); return ArrayCreationExpression(arrayType, initializer); } - private static SeparatedSyntaxList CreateSimpleLambdaExpressions(InitializerExpressionSyntax initializerExpressionSyntax) + private static SeparatedSyntaxList CreateSimpleLambdaExpressions(IEnumerable initializers) { - var expressions = initializerExpressionSyntax.Expressions.Select(CreateSimpleLambdaExpressionNode); + var expressions = initializers.Select(CreateSimpleLambdaExpressionNode); return SeparatedList(expressions); } private static ArrayTypeSyntax CreateArrayTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type) { - var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type); - var typeArgumentListSyntax = TypeArgumentList( - SeparatedList( - new SyntaxNodeOrToken[] - { - QualifiedName( - QualifiedName( - IdentifierName("NSubstitute"), - IdentifierName("Core")), - IdentifierName("CallInfo")), - Token(SyntaxKind.CommaToken), - typeSyntax - })); - - var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax)); - + var callbackTypeSyntax = syntaxGenerator.CallInfoCallbackTypeSyntax(type); var arrayRankSpecifierSyntaxes = SingletonList(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression()))); - return ArrayType(qualifiedNameSyntax, arrayRankSpecifierSyntaxes).WithAdditionalAnnotations(Simplifier.Annotation); + return ArrayType((TypeSyntax)callbackTypeSyntax, arrayRankSpecifierSyntaxes).WithAdditionalAnnotations(Simplifier.Annotation); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs index d3772c89..f94a3ebb 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractReEntrantSetupCodeFixProvider.cs @@ -20,7 +20,7 @@ internal abstract class AbstractReEntrantSetupCodeFixProvider initializers); protected abstract SyntaxNode GetArgumentExpressionSyntax(TArgumentSyntax argumentSyntax); @@ -74,18 +74,23 @@ private async Task CreateChangedDocument( return context.Document; } - var skip = methodSymbol.MethodKind == MethodKind.Ordinary - ? 1 - : 0; - - foreach (var argumentSyntax in argumentSyntaxes.Skip(skip)) + var syntaxGenerator = SyntaxGenerator.GetGenerator(context.Document); + foreach (var argumentSyntax in argumentSyntaxes) { - if (IsArrayParamsArgument(semanticModel, argumentSyntax)) + var operation = semanticModel.GetOperation(argumentSyntax) as IArgumentOperation; + if (operation != null && (methodSymbol.MethodKind == MethodKind.Ordinary && operation.Parameter.Ordinal == 0)) + { + continue; + } + + if (IsArrayParamsArgument(operation)) { + var initializers = GetInitializers(operation); var updatedParamsArgumentSyntaxNode = CreateUpdatedParamsArgumentSyntaxNode( - SyntaxGenerator.GetGenerator(context.Document), + syntaxGenerator, methodSymbol.TypeArguments.FirstOrDefault() ?? methodSymbol.ReceiverType, - argumentSyntax); + argumentSyntax, + initializers); documentEditor.ReplaceNode(argumentSyntax, updatedParamsArgumentSyntaxNode); } @@ -115,13 +120,16 @@ private bool IsFixSupported(SemanticModel semanticModel, IEnumerable argumentOperation != null && argumentOperation.Parameter.IsParams; + + private IEnumerable GetInitializers(IArgumentOperation argumentOperation) { - var argumentListSyntax = diagnosticNode.Ancestors().OfType().FirstOrDefault(); - return argumentListSyntax; + return (argumentOperation.Value as IArrayCreationOperation).Initializer.ElementValues.Select(value => value.Syntax); } + + private static TArgumentListSyntax GetArgumentListSyntax(SyntaxNode diagnosticNode) => diagnosticNode.Ancestors().OfType().FirstOrDefault(); } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs index 1bdcdcb6..ad09b82f 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/IOperationExtensions.cs @@ -40,26 +40,6 @@ public static IEnumerable GetOrderedArgumentOperationsWithou return orderedArguments.Skip(1); } - public static IEnumerable GetArgumentOperationsWithoutInstanceArgument(this IInvocationOperation invocationOperation) - { - var orderedArguments = invocationOperation.Arguments - .Where(arg => IsImplicitlyProvidedArrayWithoutValues(arg) == false); - - if (!invocationOperation.TargetMethod.IsExtensionMethod) - { - return orderedArguments; - } - - // unlike CSharp implementation, VisualBasic doesnt include "instance" argument for reduced extensions - if (invocationOperation.TargetMethod.MethodKind == MethodKind.ReducedExtension && - invocationOperation.Language == LanguageNames.VisualBasic) - { - return orderedArguments; - } - - return orderedArguments.Where(x => x.Parameter.Ordinal != 0); - } - public static IEnumerable GetOrderedArgumentOperations(this IInvocationOperation invocationOperation) { return invocationOperation.Arguments.OrderBy(arg => arg.Parameter.Ordinal); diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxGeneratorExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxGeneratorExtensions.cs index 09f4300e..c8756fb0 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxGeneratorExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/SyntaxGeneratorExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; namespace NSubstitute.Analyzers.Shared.Extensions { @@ -9,9 +10,7 @@ public static SyntaxNode SubstituteForInvocationExpression( this SyntaxGenerator syntaxGenerator, IParameterSymbol parameterSymbol) { - var qualifiedName = syntaxGenerator.QualifiedName( - syntaxGenerator.IdentifierName("NSubstitute"), - syntaxGenerator.IdentifierName("Substitute")); + var qualifiedName = syntaxGenerator.DottedName("NSubstitute.Substitute"); var genericName = syntaxGenerator.GenericName("For", syntaxGenerator.TypeExpression(parameterSymbol.Type)); @@ -22,6 +21,17 @@ public static SyntaxNode SubstituteForInvocationExpression( return invocationExpression; } + public static SyntaxNode CallInfoCallbackTypeSyntax( + this SyntaxGenerator syntaxGenerator, + ITypeSymbol returnedType) + { + var typeSyntax = syntaxGenerator.TypeExpression(returnedType); + var genericName = syntaxGenerator.GenericName("Func", syntaxGenerator.DottedName("NSubstitute.Core.CallInfo"), typeSyntax); + var qualifiedNameSyntax = syntaxGenerator.QualifiedName(syntaxGenerator.IdentifierName("System"), genericName); + + return qualifiedNameSyntax.WithAdditionalAnnotations(Simplifier.Annotation); + } + public static SyntaxNode InternalVisibleToDynamicProxyAttributeList(this SyntaxGenerator syntaxGenerator) { var attributeArguments = diff --git a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs index cfa81a46..e8954391 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/CodeFixProviders/ReEntrantSetupCodeFixProvider.cs @@ -1,13 +1,12 @@ -using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Syntax; using NSubstitute.Analyzers.Shared.CodeFixProviders; +using NSubstitute.Analyzers.Shared.Extensions; using NSubstitute.Analyzers.VisualBasic.Extensions; using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; @@ -31,41 +30,22 @@ protected override ArgumentSyntax CreateUpdatedArgumentSyntaxNode(ArgumentSyntax return argumentSyntaxNode; } - protected override ArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode(SyntaxGenerator syntaxGenerator, ITypeSymbol typeSymbol, ArgumentSyntax argumentSyntaxNode) + protected override ArgumentSyntax CreateUpdatedParamsArgumentSyntaxNode( + SyntaxGenerator syntaxGenerator, + ITypeSymbol typeSymbol, + ArgumentSyntax argumentSyntaxNode, + IEnumerable initializers) { if (!(argumentSyntaxNode is SimpleArgumentSyntax simpleArgumentSyntax)) { return argumentSyntaxNode; } - var expression = argumentSyntaxNode.GetExpression(); - ArrayCreationExpressionSyntax resultArrayCreationExpressionSyntax; - - switch (expression) - { - case ArrayCreationExpressionSyntax arrayCreationExpressionSyntax: - resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( - syntaxGenerator, - typeSymbol, - arrayCreationExpressionSyntax.Initializer.Initializers); - break; - case CollectionInitializerSyntax implicitArrayCreationExpressionSyntax: - resultArrayCreationExpressionSyntax = CreateArrayCreationExpression( - syntaxGenerator, - typeSymbol, - implicitArrayCreationExpressionSyntax.Initializers); - break; - default: - throw new ArgumentException($"{argumentSyntaxNode.Kind()} is not recognized as array initialization", nameof(argumentSyntaxNode)); - } - - return simpleArgumentSyntax.WithExpression(resultArrayCreationExpressionSyntax); + var arrayCreationExpression = CreateArrayCreationExpression(syntaxGenerator, typeSymbol, initializers); + return simpleArgumentSyntax.WithExpression(arrayCreationExpression); } - protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) - { - return argumentSyntax.GetExpression(); - } + protected override SyntaxNode GetArgumentExpressionSyntax(ArgumentSyntax argumentSyntax) => argumentSyntax.GetExpression(); protected override IEnumerable GetParameterExpressionsFromArrayArgument(ArgumentSyntax argumentSyntaxNode) { @@ -74,23 +54,21 @@ protected override IEnumerable GetParameterExpressionsFromArrayArgum protected override int AwaitExpressionRawKind { get; } = (int)SyntaxKind.AwaitExpression; - protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) - { - return argumentSyntax.Arguments; - } + protected override IEnumerable GetArguments(ArgumentListSyntax argumentSyntax) => argumentSyntax.Arguments; private static ArrayCreationExpressionSyntax CreateArrayCreationExpression( SyntaxGenerator syntaxGenerator, ITypeSymbol typeSymbol, - SeparatedSyntaxList initializers) + IEnumerable initializers) { - var typeNode = CreateTypeNode(syntaxGenerator, typeSymbol); - var syntaxes = CreateSingleLineLambdaExpressions(initializers); + var initializersSyntaxList = SeparatedList(initializers); + var typeNode = syntaxGenerator.CallInfoCallbackTypeSyntax(typeSymbol); + var syntaxes = CreateSingleLineLambdaExpressions(initializersSyntaxList); var initializer = CollectionInitializer(syntaxes); var arrayRankSpecifierSyntaxes = SingletonList(ArrayRankSpecifier()); - return ArrayCreationExpression(Token(SyntaxKind.NewKeyword), new SyntaxList(), typeNode, null, arrayRankSpecifierSyntaxes, initializer); + return ArrayCreationExpression(Token(SyntaxKind.NewKeyword), new SyntaxList(), (TypeSyntax)typeNode, null, arrayRankSpecifierSyntaxes, initializer); } private static SeparatedSyntaxList CreateSingleLineLambdaExpressions(SeparatedSyntaxList expressions) @@ -115,26 +93,5 @@ private static SingleLineLambdaExpressionSyntax CreateSingleLineLambdaExpression expressionSyntax.WithLeadingTrivia()); return lambdaExpression; } - - private static QualifiedNameSyntax CreateTypeNode(SyntaxGenerator syntaxGenerator, ITypeSymbol type) - { - var typeSyntax = (TypeSyntax)syntaxGenerator.TypeExpression(type); - var typeArgumentListSyntax = TypeArgumentList( - SeparatedList( - new SyntaxNodeOrToken[] - { - QualifiedName( - QualifiedName( - IdentifierName("NSubstitute"), - IdentifierName("Core")), - IdentifierName("CallInfo")), - Token(SyntaxKind.CommaToken), - typeSyntax - })); - - var qualifiedNameSyntax = QualifiedName(IdentifierName("System"), GenericName(Identifier("Func"), typeArgumentListSyntax)); - - return qualifiedNameSyntax.WithAdditionalAnnotations(Simplifier.Annotation); - } } } \ No newline at end of file diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs index c3ff1c2c..0d5a148f 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsExtensionMethodTests.cs @@ -112,6 +112,7 @@ public void Test() { var secondSubstitute = Substitute.For(); secondSubstitute.Id.Returns(CreateReEntrantSubstitute(), new[] { MyNamespace.FooTests.Value }); + secondSubstitute.Id.Returns(returnThis: CreateReEntrantSubstitute(), returnThese: new[] { MyNamespace.FooTests.Value }); } private int CreateReEntrantSubstitute() @@ -149,6 +150,7 @@ public void Test() { var secondSubstitute = Substitute.For(); secondSubstitute.Id.Returns(_ => CreateReEntrantSubstitute(), new Func[] { _ => MyNamespace.FooTests.Value }); + secondSubstitute.Id.Returns(returnThis: _ => CreateReEntrantSubstitute(), returnThese: new Func[] { _ => MyNamespace.FooTests.Value }); } private int CreateReEntrantSubstitute() diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs index 6cbbc148..253bc547 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs @@ -112,6 +112,8 @@ public void Test() { var secondSubstitute = Substitute.For(); SubstituteExtensions.Returns(secondSubstitute.Id, CreateReEntrantSubstitute(), new[] { MyNamespace.FooTests.Value }); + SubstituteExtensions.Returns(value: secondSubstitute.Id, returnThis: CreateReEntrantSubstitute(), returnThese: new[] { MyNamespace.FooTests.Value }); + SubstituteExtensions.Returns(returnThis: CreateReEntrantSubstitute(), returnThese: new[] { MyNamespace.FooTests.Value }, value: secondSubstitute.Id); } private int CreateReEntrantSubstitute() @@ -149,6 +151,8 @@ public void Test() { var secondSubstitute = Substitute.For(); SubstituteExtensions.Returns(secondSubstitute.Id, _ => CreateReEntrantSubstitute(), new Func[] { _ => MyNamespace.FooTests.Value }); + SubstituteExtensions.Returns(value: secondSubstitute.Id, returnThis: _ => CreateReEntrantSubstitute(), returnThese: new Func[] { _ => MyNamespace.FooTests.Value }); + SubstituteExtensions.Returns(returnThis: _ => CreateReEntrantSubstitute(), returnThese: new Func[] { _ => MyNamespace.FooTests.Value }, value: secondSubstitute.Id); } private int CreateReEntrantSubstitute() diff --git a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs index 38eed572..67f4e801 100644 --- a/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.VisualBasic/CodeFixProvidersTests/ReEntrantSetupCodeFixProviderTests/ReturnsAsOrdinaryMethodTests.cs @@ -94,6 +94,8 @@ End Interface Public Sub Test() Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() SubstituteExtensions.Returns(secondSubstitute.Id, CreateReEntrantSubstitute(), {MyNamespace.FooTests.Value}) + SubstituteExtensions.Returns(value:= secondSubstitute.Id, returnThis:= CreateReEntrantSubstitute()) + SubstituteExtensions.Returns(returnThis:= CreateReEntrantSubstitute(), value:= secondSubstitute.Id) End Sub Private Function CreateReEntrantSubstitute() As Integer @@ -125,6 +127,8 @@ End Interface Public Sub Test() Dim secondSubstitute = NSubstitute.Substitute.[For](Of IFoo)() SubstituteExtensions.Returns(secondSubstitute.Id, Function(x) CreateReEntrantSubstitute(), New Func(Of CallInfo, Integer)() {Function(x) MyNamespace.FooTests.Value}) + SubstituteExtensions.Returns(value:= secondSubstitute.Id, returnThis:=Function(x) CreateReEntrantSubstitute()) + SubstituteExtensions.Returns(returnThis:=Function(x) CreateReEntrantSubstitute(), value:= secondSubstitute.Id) End Sub Private Function CreateReEntrantSubstitute() As Integer