Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

Commit

Permalink
Add support for indexers
Browse files Browse the repository at this point in the history
  • Loading branch information
nbilal authored and nbilal committed Sep 5, 2016
1 parent f39e72b commit b93154c
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 78 deletions.
22 changes: 22 additions & 0 deletions Documentation/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ var stub = new StubIPhoneBook()
.MyNumber_Set(value => newNumber = value);
```

## Stubbing indexers
```csharp
var stub = new StubIGenericContainer<int>();

// stubbing indexer getter
stub.Item_Get(index =>
{
// we're expecting the code under test to get index 5
if (index != 5) throw new IndexOutOfRangeException();
return 99;
});

// stubbing indexer setter
int res = -1;
stub.Item_Set((index, value) =>
{
// we're expecting the code under test to only set index 7
if (index != 7) throw new IndexOutOfRangeException();
res = value;
});
```

## Stubbing events

```csharp
Expand Down
2 changes: 1 addition & 1 deletion NuGet/SimpleStubs.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package >
<metadata>
<id>Etg.SimpleStubs</id>
<version>2.2.0</version>
<version>2.3.0</version>
<title>SimpleStubs mocking framework</title>
<authors>Microsoft Studios (BigPark)</authors>
<owners>Microsoft Studios (BigPark)</owners>
Expand Down
11 changes: 11 additions & 0 deletions src/SimpleStubs.CodeGen/CodeGen/IPropertyStubber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Etg.SimpleStubs.CodeGen
{
internal interface IPropertyStubber
{
ClassDeclarationSyntax StubProperty(ClassDeclarationSyntax classDclr, IPropertySymbol propertySymbol,
INamedTypeSymbol stubbedInterface);
}
}
20 changes: 18 additions & 2 deletions src/SimpleStubs.CodeGen/CodeGen/InterfaceStubber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ namespace Etg.SimpleStubs.CodeGen
internal class InterfaceStubber : IInterfaceStubber
{
private readonly IEnumerable<IMethodStubber> _methodStubbers;
private readonly IEnumerable<IPropertyStubber> _propertyStubbers;

public InterfaceStubber(IEnumerable<IMethodStubber> methodStubbers)
public InterfaceStubber(IEnumerable<IMethodStubber> methodStubbers, IEnumerable<IPropertyStubber> propertyStubbers)
{
_propertyStubbers = propertyStubbers;
_methodStubbers = new List<IMethodStubber>(methodStubbers);
}

Expand All @@ -32,6 +34,7 @@ public CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDe

classDclr = RoslynUtils.CopyGenericConstraints(interfaceType, classDclr);
classDclr = AddStubContainerField(classDclr, stubName);
classDclr = StubProperties(interfaceType, classDclr);
classDclr = StubMethods(interfaceType, classDclr);

string fullNameSpace = semanticModel.GetDeclaredSymbol(namespaceNode).ToString();
Expand All @@ -42,9 +45,22 @@ public CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDe
return cu;
}

private ClassDeclarationSyntax StubProperties(INamedTypeSymbol interfaceType, ClassDeclarationSyntax classDclr)
{
IEnumerable<IPropertySymbol> propertiesToStub = RoslynUtils.GetAllMembers<IPropertySymbol>(interfaceType);
foreach (IPropertySymbol propertySymbol in propertiesToStub)
{
foreach (IPropertyStubber propertyStubber in _propertyStubbers)
{
classDclr = propertyStubber.StubProperty(classDclr, propertySymbol, interfaceType);
}
}
return classDclr;
}

private ClassDeclarationSyntax StubMethods(INamedTypeSymbol interfaceType, ClassDeclarationSyntax classDclr)
{
List<IMethodSymbol> methodsToStub = RoslynUtils.GetAllMethods(interfaceType);
IEnumerable<IMethodSymbol> methodsToStub = RoslynUtils.GetAllMembers<IMethodSymbol>(interfaceType);
foreach (IMethodSymbol methodSymbol in methodsToStub)
{
foreach (IMethodStubber methodStubber in _methodStubbers)
Expand Down
18 changes: 1 addition & 17 deletions src/SimpleStubs.CodeGen/CodeGen/OrdinaryMethodStubber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMeth
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));

string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string parameters = FormatParameters(methodSymbol);
string parameters = StubbingUtils.FormatParameters(methodSymbol);

string callDelegateStmt = StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, methodSymbol.GetGenericName(), parameters);
if (!methodSymbol.ReturnsVoid)
Expand All @@ -40,21 +40,5 @@ public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMeth

return classDclr;
}

private static string FormatParameters(IMethodSymbol methodSymbol)
{
return string.Join(", ", methodSymbol.Parameters.Select(p =>
{
if (p.RefKind == RefKind.Out)
{
return $"out {p.Name}";
}
if (p.RefKind == RefKind.Ref)
{
return $"ref {p.Name}";
}
return p.Name;
}));
}
}
}
86 changes: 41 additions & 45 deletions src/SimpleStubs.CodeGen/CodeGen/PropertyStubber.cs
Original file line number Diff line number Diff line change
@@ -1,79 +1,75 @@
using System;
using Etg.SimpleStubs.CodeGen.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Etg.SimpleStubs.CodeGen.Utils;

namespace Etg.SimpleStubs.CodeGen
{
using Microsoft.CodeAnalysis.CSharp;
using System.Linq;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using SF = SyntaxFactory;

internal class PropertyStubber : IMethodStubber
internal class PropertyStubber : IPropertyStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
public ClassDeclarationSyntax StubProperty(ClassDeclarationSyntax classDclr, IPropertySymbol propertySymbol,
INamedTypeSymbol stubbedInterface)
{
if (!methodSymbol.IsPropertyAccessor())
{
return classDclr;
}

string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string indexerType = propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
BasePropertyDeclarationSyntax propDclr = null;

string propName = methodSymbol.AssociatedSymbol.Name;
string propType =
((IPropertySymbol) methodSymbol.AssociatedSymbol).Type.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
var propDclr = GetPropDclr(classDclr, propName);
if (propDclr == null)
if (propertySymbol.GetMethod != null)
{
propDclr = SF.PropertyDeclaration(SF.ParseTypeName(propType), SF.Identifier(propName))
.WithExplicitInterfaceSpecifier(SF.ExplicitInterfaceSpecifier(
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
}
IMethodSymbol getMethodSymbol = propertySymbol.GetMethod;
string parameters = StubbingUtils.FormatParameters(getMethodSymbol);

if (methodSymbol.IsPropertyGetter())
{
string delegateTypeName = NamingUtils.GetDelegateTypeName(getMethodSymbol, stubbedInterface);
var accessorDclr = SF.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, SF.Block(
SF.List(new[]
{
SF.ParseStatement("return " + StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, methodSymbol.Name, ""))
SF.ParseStatement("return " + StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, getMethodSymbol.Name, parameters))
})));

propDclr = CreatePropertyDclr(getMethodSymbol, indexerType);
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);

}
else if (methodSymbol.IsPropertySetter())
if (propertySymbol.SetMethod != null)
{
IMethodSymbol setMethodSymbol = propertySymbol.SetMethod;
string parameters = $"{StubbingUtils.FormatParameters(setMethodSymbol)}";
string delegateTypeName = NamingUtils.GetDelegateTypeName(setMethodSymbol, stubbedInterface);
var accessorDclr = SF.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, SF.Block(
SF.List(new[]
{
SF.ParseStatement(StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, methodSymbol.Name, "value"))
SF.ParseStatement(StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, setMethodSymbol.Name, parameters))
})));
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
}

if (propDclr != null)
{
PropertyDeclarationSyntax existingPropDclr = GetPropDclr(classDclr, propName);
if (existingPropDclr != null)
if (propDclr == null)
{
classDclr = classDclr.ReplaceNode(existingPropDclr, propDclr);
}
else
{
classDclr = classDclr.AddMembers(propDclr);
propDclr = CreatePropertyDclr(setMethodSymbol, indexerType);
}
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
}

classDclr = classDclr.AddMembers(propDclr);
return classDclr;
}

private static PropertyDeclarationSyntax GetPropDclr(ClassDeclarationSyntax classDclr, string propName)
private BasePropertyDeclarationSyntax CreatePropertyDclr(IMethodSymbol methodSymbol, string propType)
{
return
classDclr.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.FirstOrDefault(p => p.Identifier.Text == propName);
if (methodSymbol.IsIndexerAccessor())
{
IndexerDeclarationSyntax indexerDclr = SF.IndexerDeclaration(
SF.ParseTypeName(propType))
.WithExplicitInterfaceSpecifier(SF.ExplicitInterfaceSpecifier(
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
indexerDclr = indexerDclr.AddParameterListParameters(
RoslynUtils.GetMethodParameterSyntaxList(methodSymbol).ToArray());
return indexerDclr;
}

string propName = methodSymbol.AssociatedSymbol.Name;
PropertyDeclarationSyntax propDclr = SF.PropertyDeclaration(SF.ParseTypeName(propType), SF.Identifier(propName))
.WithExplicitInterfaceSpecifier(SF.ExplicitInterfaceSpecifier(
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
return propDclr;
}
}
}
5 changes: 4 additions & 1 deletion src/SimpleStubs.CodeGen/DI/DIModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ public static ContainerBuilder RegisterTypes(string testProjectPath, string stub
{
new OrdinaryMethodStubber(),
new EventStubber(),
new PropertyStubber(),
new StubbingDelegateGenerator()
},
new IPropertyStubber[]
{
new PropertyStubber()
});
return interfaceStubber;
}).As<IInterfaceStubber>().SingleInstance();
Expand Down
1 change: 1 addition & 0 deletions src/SimpleStubs.CodeGen/SimpleStubs.CodeGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<Compile Include="CodeGen\IInterfaceStubber.cs" />
<Compile Include="CodeGen\InterfaceStubber.cs" />
<Compile Include="CodeGen\IProjectStubber.cs" />
<Compile Include="CodeGen\IPropertyStubber.cs" />
<Compile Include="CodeGen\ProjectStubber.cs" />
<Compile Include="Utils\StubbingUtils.cs" />
<Compile Include="CodeGen\StubProjectResult.cs" />
Expand Down
2 changes: 1 addition & 1 deletion src/SimpleStubs.CodeGen/Utils/NamingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static string GetDelegateTypeName(IMethodSymbol methodSymbol, INamedTypeS
methodName = SerializeName(methodSymbol.ContainingSymbol) + "_" + methodName;
}

if (methodSymbol.IsOrdinaryMethod())
if (methodSymbol.IsOrdinaryMethod() || methodSymbol.IsIndexerAccessor())
{
if (methodSymbol.Parameters.Any())
{
Expand Down
34 changes: 33 additions & 1 deletion src/SimpleStubs.CodeGen/Utils/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ public static bool IsPropertyGetter(this IMethodSymbol methodSymbol)
return methodSymbol.MethodKind == MethodKind.PropertyGet;
}

public static bool IsIndexerGetter(this IMethodSymbol methodSymbol)
{
return methodSymbol.Name == "get_Item";
}

public static bool IsIndexerSetter(this IMethodSymbol methodSymbol)
{
return methodSymbol.Name == "set_Item";
}

public static bool IsIndexerAccessor(this IMethodSymbol methodSymbol)
{
IPropertySymbol propertySymbol = methodSymbol.AssociatedSymbol as IPropertySymbol;
return propertySymbol != null && propertySymbol.IsIndexer;
}

public static string GetGenericName(this IMethodSymbol methodSymbol)
{
string name = methodSymbol.Name;
Expand All @@ -52,7 +68,7 @@ public static string GetGenericName(this IMethodSymbol methodSymbol)
return name;
}

public static string GetContainingInterfaceGenericQualifiedName(this IMethodSymbol methodSymbol)
public static string GetContainingInterfaceGenericQualifiedName(this ISymbol methodSymbol)
{
return methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
Expand Down Expand Up @@ -102,5 +118,21 @@ public static bool IsInternal(this TypeDeclarationSyntax typeDclr)
};
return !typeDclr.Modifiers.Any(modifier => nonInternalModifiers.Contains(modifier.RawKind));
}

public static BasePropertyDeclarationSyntax AddAccessorListAccessors(this BasePropertyDeclarationSyntax baseDclr, params AccessorDeclarationSyntax[] accessors)
{
var propDclr = baseDclr as PropertyDeclarationSyntax;
if (propDclr != null)
{
return propDclr.AddAccessorListAccessors(accessors);
}

var indexerDclr = baseDclr as IndexerDeclarationSyntax;
if (indexerDclr != null)
{
return indexerDclr.AddAccessorListAccessors(accessors);
}
throw new InvalidOperationException();
}
}
}
12 changes: 6 additions & 6 deletions src/SimpleStubs.CodeGen/Utils/RoslynUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ public static List<ParameterSyntax> GetMethodParameterSyntaxList(IMethodSymbol m
return paramsSyntaxList;
}

public static List<IMethodSymbol> GetAllMethods(INamedTypeSymbol interfaceType)
public static List<T> GetAllMembers<T>(INamedTypeSymbol interfaceType)
{
var methodsToStub = new List<IMethodSymbol>(interfaceType.GetMembers().OfType<IMethodSymbol>());
methodsToStub.AddRange(GetAllInheritedMethods(interfaceType));
var methodsToStub = new List<T>(interfaceType.GetMembers().OfType<T>());
methodsToStub.AddRange(GetAllInheritedMethods<T>(interfaceType));
return methodsToStub;
}

public static IEnumerable<IMethodSymbol> GetAllInheritedMethods(ITypeSymbol typeSymbol)
public static IEnumerable<T> GetAllInheritedMethods<T>(ITypeSymbol typeSymbol)
{
var methods = new List<IMethodSymbol>();
var methods = new List<T>();
if (typeSymbol.AllInterfaces.Any())
{
foreach (var baseInterfaceType in typeSymbol.AllInterfaces)
{
methods.AddRange(baseInterfaceType.GetMembers().OfType<IMethodSymbol>());
methods.AddRange(baseInterfaceType.GetMembers().OfType<T>());
}
}

Expand Down
18 changes: 17 additions & 1 deletion src/SimpleStubs.CodeGen/Utils/StubbingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,21 @@ public static string GenerateInvokeDelegateStmt(string delegateTypeName, string
{
return $"_stubs.GetMethodStub<{delegateTypeName}>(\"{methodName}\").Invoke({parameters});\n";
}
}

public static string FormatParameters(IMethodSymbol methodSymbol)
{
return string.Join(", ", methodSymbol.Parameters.Select(p =>
{
if (p.RefKind == RefKind.Out)
{
return $"out {p.Name}";
}
if (p.RefKind == RefKind.Ref)
{
return $"ref {p.Name}";
}
return p.Name;
}));
}
}
}
Loading

0 comments on commit b93154c

Please sign in to comment.