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); .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 ## Stubbing events
```csharp ```csharp

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

@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>Etg.SimpleStubs</id> <id>Etg.SimpleStubs</id>
<version>2.2.0</version> <version>2.3.0</version>
<title>SimpleStubs mocking framework</title> <title>SimpleStubs mocking framework</title>
<authors>Microsoft Studios (BigPark)</authors> <authors>Microsoft Studios (BigPark)</authors>
<owners>Microsoft Studios (BigPark)</owners> <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 internal class InterfaceStubber : IInterfaceStubber
{ {
private readonly IEnumerable<IMethodStubber> _methodStubbers; 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); _methodStubbers = new List<IMethodStubber>(methodStubbers);
} }
@ -32,6 +34,7 @@ namespace Etg.SimpleStubs.CodeGen
classDclr = RoslynUtils.CopyGenericConstraints(interfaceType, classDclr); classDclr = RoslynUtils.CopyGenericConstraints(interfaceType, classDclr);
classDclr = AddStubContainerField(classDclr, stubName); classDclr = AddStubContainerField(classDclr, stubName);
classDclr = StubProperties(interfaceType, classDclr);
classDclr = StubMethods(interfaceType, classDclr); classDclr = StubMethods(interfaceType, classDclr);
string fullNameSpace = semanticModel.GetDeclaredSymbol(namespaceNode).ToString(); string fullNameSpace = semanticModel.GetDeclaredSymbol(namespaceNode).ToString();
@ -42,9 +45,22 @@ namespace Etg.SimpleStubs.CodeGen
return cu; 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) 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 (IMethodSymbol methodSymbol in methodsToStub)
{ {
foreach (IMethodStubber methodStubber in _methodStubbers) foreach (IMethodStubber methodStubber in _methodStubbers)

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

@ -27,7 +27,7 @@ namespace Etg.SimpleStubs.CodeGen
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName()))); SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface); string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string parameters = FormatParameters(methodSymbol); string parameters = StubbingUtils.FormatParameters(methodSymbol);
string callDelegateStmt = StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, methodSymbol.GetGenericName(), parameters); string callDelegateStmt = StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, methodSymbol.GetGenericName(), parameters);
if (!methodSymbol.ReturnsVoid) if (!methodSymbol.ReturnsVoid)
@ -40,21 +40,5 @@ namespace Etg.SimpleStubs.CodeGen
return classDclr; 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 Etg.SimpleStubs.CodeGen.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Etg.SimpleStubs.CodeGen namespace Etg.SimpleStubs.CodeGen
{ {
using Microsoft.CodeAnalysis.CSharp; using SF = SyntaxFactory;
using System.Linq;
using SF = Microsoft.CodeAnalysis.CSharp.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) INamedTypeSymbol stubbedInterface)
{ {
if (!methodSymbol.IsPropertyAccessor()) string indexerType = propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
{ BasePropertyDeclarationSyntax propDclr = null;
return classDclr;
}
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface); if (propertySymbol.GetMethod != null)
string propName = methodSymbol.AssociatedSymbol.Name;
string propType =
((IPropertySymbol) methodSymbol.AssociatedSymbol).Type.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
var propDclr = GetPropDclr(classDclr, propName);
if (propDclr == null)
{ {
propDclr = SF.PropertyDeclaration(SF.ParseTypeName(propType), SF.Identifier(propName)) IMethodSymbol getMethodSymbol = propertySymbol.GetMethod;
.WithExplicitInterfaceSpecifier(SF.ExplicitInterfaceSpecifier( string parameters = StubbingUtils.FormatParameters(getMethodSymbol);
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
}
if (methodSymbol.IsPropertyGetter()) string delegateTypeName = NamingUtils.GetDelegateTypeName(getMethodSymbol, stubbedInterface);
{
var accessorDclr = SF.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, SF.Block( var accessorDclr = SF.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, SF.Block(
SF.List(new[] 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); 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( var accessorDclr = SF.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, SF.Block(
SF.List(new[] 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); propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
} }
if (propDclr != null) classDclr = classDclr.AddMembers(propDclr);
{
PropertyDeclarationSyntax existingPropDclr = GetPropDclr(classDclr, propName);
if (existingPropDclr != null)
{
classDclr = classDclr.ReplaceNode(existingPropDclr, propDclr);
}
else
{
classDclr = classDclr.AddMembers(propDclr);
}
}
return classDclr; return classDclr;
} }
private static PropertyDeclarationSyntax GetPropDclr(ClassDeclarationSyntax classDclr, string propName) private BasePropertyDeclarationSyntax CreatePropertyDclr(IMethodSymbol methodSymbol, string propType)
{ {
return if (methodSymbol.IsIndexerAccessor())
classDclr.DescendantNodes() {
.OfType<PropertyDeclarationSyntax>() IndexerDeclarationSyntax indexerDclr = SF.IndexerDeclaration(
.FirstOrDefault(p => p.Identifier.Text == propName); 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 OrdinaryMethodStubber(),
new EventStubber(), new EventStubber(),
new PropertyStubber(),
new StubbingDelegateGenerator() new StubbingDelegateGenerator()
},
new IPropertyStubber[]
{
new PropertyStubber()
}); });
return interfaceStubber; return interfaceStubber;
}).As<IInterfaceStubber>().SingleInstance(); }).As<IInterfaceStubber>().SingleInstance();

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

@ -102,6 +102,7 @@
<Compile Include="CodeGen\IInterfaceStubber.cs" /> <Compile Include="CodeGen\IInterfaceStubber.cs" />
<Compile Include="CodeGen\InterfaceStubber.cs" /> <Compile Include="CodeGen\InterfaceStubber.cs" />
<Compile Include="CodeGen\IProjectStubber.cs" /> <Compile Include="CodeGen\IProjectStubber.cs" />
<Compile Include="CodeGen\IPropertyStubber.cs" />
<Compile Include="CodeGen\ProjectStubber.cs" /> <Compile Include="CodeGen\ProjectStubber.cs" />
<Compile Include="Utils\StubbingUtils.cs" /> <Compile Include="Utils\StubbingUtils.cs" />
<Compile Include="CodeGen\StubProjectResult.cs" /> <Compile Include="CodeGen\StubProjectResult.cs" />

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

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

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

@ -42,6 +42,22 @@ namespace Etg.SimpleStubs.CodeGen.Utils
return methodSymbol.MethodKind == MethodKind.PropertyGet; 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) public static string GetGenericName(this IMethodSymbol methodSymbol)
{ {
string name = methodSymbol.Name; string name = methodSymbol.Name;
@ -52,7 +68,7 @@ namespace Etg.SimpleStubs.CodeGen.Utils
return name; return name;
} }
public static string GetContainingInterfaceGenericQualifiedName(this IMethodSymbol methodSymbol) public static string GetContainingInterfaceGenericQualifiedName(this ISymbol methodSymbol)
{ {
return methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); return methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
} }
@ -102,5 +118,21 @@ namespace Etg.SimpleStubs.CodeGen.Utils
}; };
return !typeDclr.Modifiers.Any(modifier => nonInternalModifiers.Contains(modifier.RawKind)); 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; 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>()); var methodsToStub = new List<T>(interfaceType.GetMembers().OfType<T>());
methodsToStub.AddRange(GetAllInheritedMethods(interfaceType)); methodsToStub.AddRange(GetAllInheritedMethods<T>(interfaceType));
return methodsToStub; 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()) if (typeSymbol.AllInterfaces.Any())
{ {
foreach (var baseInterfaceType in typeSymbol.AllInterfaces) 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"; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace TestClassLibrary namespace TestClassLibrary
@ -20,12 +21,18 @@ namespace TestClassLibrary
event EventHandler<long> PhoneNumberChanged; event EventHandler<long> PhoneNumberChanged;
} }
// just to make sure all inherited members are stubbed
public interface IPhoneBookSpecial : IPhoneBook
{
}
public interface IContainer public interface IContainer
{ {
T GetElement<T>(int index); T GetElement<T>(int index);
void SetElement<T>(int index, T value); void SetElement<T>(int index, T value);
bool GetElement(int index, out object value); bool GetElement(int index, out object value);
} }
public interface IRefUtils public interface IRefUtils
@ -74,6 +81,18 @@ namespace TestClassLibrary
T GetX(); 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 public interface IInterfaceWithGenericMethod
{ {
T GetFoo<T>() where T : class; T GetFoo<T>() where T : class;

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

@ -102,7 +102,7 @@ namespace TestClassLibraryTest
[TestMethod] [TestMethod]
[ExpectedException(typeof(SimpleStubsException))] [ExpectedException(typeof(SimpleStubsException))]
public void TestThatExceptionIsThrownWhenMethodIsCalledMoreThanExpected() public void TestThatExceptionIsThrownWhenMethodIsCalledMoreThanExpected()
{ {
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => 12345678, Times.Once); var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => 12345678, Times.Once);
@ -115,7 +115,7 @@ namespace TestClassLibraryTest
public void TestThatMethodStubCanBeOverwritten() public void TestThatMethodStubCanBeOverwritten()
{ {
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => 12345678); 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; IPhoneBook phoneBook = stub;
Assert.AreEqual(11122233, phoneBook.GetContactPhoneNumber("John", "Smith")); Assert.AreEqual(11122233, phoneBook.GetContactPhoneNumber("John", "Smith"));
@ -165,12 +165,63 @@ namespace TestClassLibraryTest
int i1 = 1; int i1 = 1;
int i2 = 2; 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(2, i1);
Assert.AreEqual(1, i2); 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 // this test is only used for debugging
[Ignore]
[TestMethod] [TestMethod]
public async Task TestGenerateStubs() public async Task TestGenerateStubs()
{ {