This commit is contained in:
nbilal 2016-09-05 11:06:01 -07:00
Родитель f39e72bf43
Коммит b93154c152
14 изменённых файлов: 231 добавлений и 80 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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>

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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);
}
@ -32,6 +34,7 @@ namespace Etg.SimpleStubs.CodeGen
classDclr = RoslynUtils.CopyGenericConstraints(interfaceType, classDclr);
classDclr = AddStubContainerField(classDclr, stubName);
classDclr = StubProperties(interfaceType, classDclr);
classDclr = StubMethods(interfaceType, classDclr);
string fullNameSpace = semanticModel.GetDeclaredSymbol(namespaceNode).ToString();
@ -42,9 +45,22 @@ namespace Etg.SimpleStubs.CodeGen
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)

Просмотреть файл

@ -27,7 +27,7 @@ namespace Etg.SimpleStubs.CodeGen
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)
@ -40,21 +40,5 @@ namespace Etg.SimpleStubs.CodeGen
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;
}));
}
}
}

Просмотреть файл

@ -1,79 +1,75 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Etg.SimpleStubs.CodeGen.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
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 indexerType = propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
BasePropertyDeclarationSyntax propDclr = null;
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
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))
})));
if (propDclr == null)
{
propDclr = CreatePropertyDclr(setMethodSymbol, indexerType);
}
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
}
if (propDclr != null)
{
PropertyDeclarationSyntax existingPropDclr = GetPropDclr(classDclr, propName);
if (existingPropDclr != null)
{
classDclr = classDclr.ReplaceNode(existingPropDclr, propDclr);
}
else
{
classDclr = classDclr.AddMembers(propDclr);
}
}
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;
}
}
}

Просмотреть файл

@ -30,8 +30,11 @@ namespace Etg.SimpleStubs.CodeGen.DI
{
new OrdinaryMethodStubber(),
new EventStubber(),
new PropertyStubber(),
new StubbingDelegateGenerator()
},
new IPropertyStubber[]
{
new PropertyStubber()
});
return interfaceStubber;
}).As<IInterfaceStubber>().SingleInstance();

Просмотреть файл

@ -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" />

Просмотреть файл

@ -42,7 +42,7 @@ namespace Etg.SimpleStubs.CodeGen.Utils
methodName = SerializeName(methodSymbol.ContainingSymbol) + "_" + methodName;
}
if (methodSymbol.IsOrdinaryMethod())
if (methodSymbol.IsOrdinaryMethod() || methodSymbol.IsIndexerAccessor())
{
if (methodSymbol.Parameters.Any())
{

Просмотреть файл

@ -42,6 +42,22 @@ namespace Etg.SimpleStubs.CodeGen.Utils
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;
@ -52,7 +68,7 @@ namespace Etg.SimpleStubs.CodeGen.Utils
return name;
}
public static string GetContainingInterfaceGenericQualifiedName(this IMethodSymbol methodSymbol)
public static string GetContainingInterfaceGenericQualifiedName(this ISymbol methodSymbol)
{
return methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
@ -102,5 +118,21 @@ namespace Etg.SimpleStubs.CodeGen.Utils
};
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();
}
}
}

Просмотреть файл

@ -55,21 +55,21 @@ namespace Etg.SimpleStubs.CodeGen.Utils
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>());
}
}

Просмотреть файл

@ -9,5 +9,21 @@ namespace Etg.SimpleStubs.CodeGen.Utils
{
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;
}));
}
}
}

Просмотреть файл

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace TestClassLibrary
@ -20,12 +21,18 @@ namespace TestClassLibrary
event EventHandler<long> PhoneNumberChanged;
}
// just to make sure all inherited members are stubbed
public interface IPhoneBookSpecial : IPhoneBook
{
}
public interface IContainer
{
T GetElement<T>(int index);
void SetElement<T>(int index, T value);
bool GetElement(int index, out object value);
}
public interface IRefUtils
@ -74,6 +81,18 @@ namespace TestClassLibrary
T GetX();
}
public interface IGenericContainer<T>
{
T this[int index] { get; set; }
T this[string key, int n] { get; }
}
// just to make sure inherited indexers are stubbed
public interface IGenericContainerSubInterface : IGenericContainer<int>
{
}
public interface IInterfaceWithGenericMethod
{
T GetFoo<T>() where T : class;

Просмотреть файл

@ -102,7 +102,7 @@ namespace TestClassLibraryTest
[TestMethod]
[ExpectedException(typeof(SimpleStubsException))]
public void TestThatExceptionIsThrownWhenMethodIsCalledMoreThanExpected()
{
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => 12345678, Times.Once);
@ -115,7 +115,7 @@ namespace TestClassLibraryTest
public void TestThatMethodStubCanBeOverwritten()
{
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => 12345678);
stub.GetContactPhoneNumber((p1, p2) => 11122233, overwrite:true);
stub.GetContactPhoneNumber((p1, p2) => 11122233, overwrite: true);
IPhoneBook phoneBook = stub;
Assert.AreEqual(11122233, phoneBook.GetContactPhoneNumber("John", "Smith"));
@ -165,12 +165,63 @@ namespace TestClassLibraryTest
int i1 = 1;
int i2 = 2;
((IRefUtils) stub).Swap<int>(ref i1, ref i2);
((IRefUtils)stub).Swap<int>(ref i1, ref i2);
Assert.AreEqual(2, i1);
Assert.AreEqual(1, i2);
}
[TestMethod]
public void TestIndexerGet()
{
var stub = new StubIGenericContainer<int>();
stub.Item_Get(index =>
{
switch (index)
{
case 0:
return 13;
case 1:
return 5;
default:
throw new IndexOutOfRangeException();
}
});
IGenericContainer<int> container = stub;
Assert.AreEqual(13, container[0]);
Assert.AreEqual(5, container[1]);
}
[TestMethod]
public void TestIndexerSet()
{
var stub = new StubIGenericContainer<int>();
int res = -1;
stub.Item_Set((index, value) =>
{
if (index != 0) throw new IndexOutOfRangeException();
res = value;
});
IGenericContainer<int> container = stub;
container[0] = 13;
Assert.AreEqual(13, res);
}
[TestMethod]
public void TestThatMultipleIndexerDontConflict()
{
var stub = new StubIGenericContainer<int>();
stub.Item_Get(index => 12).Item_Get((key, i) => 3);
IGenericContainer<int> container = stub;
Assert.AreEqual(12, container[0]);
Assert.AreEqual(3, container["foo", 0]);
}
// this test is only used for debugging
[Ignore]
[TestMethod]
public async Task TestGenerateStubs()
{