Support generic methods and ref/out parameters

This commit also involves a major change in SimpleStubs Api.
Properties that were used for stubbing have been replaced with setup
methods that have the same name as the methods being stubbed. The new Api
is fluent, simple and more readeable (no more _int_string naming style).
This commit is contained in:
Nehme Bilal 2016-07-10 11:57:16 -07:00
Родитель 808afa7267
Коммит 8de7730ed6
30 изменённых файлов: 516 добавлений и 473 удалений

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

@ -29,28 +29,19 @@ public interface IPhoneBook
### Stubbing methods
```csharp
var stub = new StubIPhoneBook
{
GetContactPhoneNumber_String_String = (fn, ln) =>
{
return 6041234567;
}
};
var stub = new StubIPhoneBook().GetContactPhoneNumber((firstName, lastName) => 6041234567);
```
You can also copy and verify the parameters values:
```csharp
string firstName = null;
string lastName = null;
var stub = new StubIPhoneBook
var stub = new StubIPhoneBook().GetContactPhoneNumber((fn, ln) =>
{
GetContactPhoneNumber_String_String = (fn, ln) =>
{
firstName = fn;
lastName = ln;
return 6041234567;
}
};
firstName = fn;
lastName = ln;
return number;
});
ClassUnderTest obj = new ClassUnderTest(stub);
@ -60,18 +51,43 @@ Assert.AreEqual("John", firstName);
Assert.AreEqual("Smith", lastName);
```
### Out parameters
```csharp
object someObj = new Foo();
var stub = new StubIContainer()
.GetElement((int index, out object value) =>
{
value = someObj;
return true;
});
```
### Ref parameters
```csharp
var stub = new StubIRefUtils()
.Swap<int>((ref int v1, ref int v2) =>
{
int temp = v1;
v1 = v2;
v2 = temp;
});
```
### Generic methods
```csharp
int value = -1;
var stub = new StubIContainer()
.GetElement<int>(index => value)
.SetElement<int>((i, v) => { value = v; });
```
## Stubbing properties
```csharp
long myNumber = 6041234567;
var stub = new StubIPhoneBook
{
MyNumber_Get = () => myNumber,
MyNumber_Set = num =>
{
myNumber = num;
}
};
var stub = new StubIPhoneBook()
.MyNumber_Get(() => myNumber)
.MyNumber_Set(value => newNumber = value);
```
## Stubbing events
@ -99,139 +115,12 @@ var sequence = StubsUtils.Sequence<Func<string, string, int>>()
.Repeat((p1, p2) => 11122233, 3) // next three calls will return 11122233
.Forever((p1, p2) => 22233556); // any subsequent call will return 22233556
var stub = new StubIPhoneBook
{
// Get the next element from the sequence every time the method is called and invoke it
GetContactPhoneNumber_String_String = (p1, p2) => sequence.Next(p1, p2)
};
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => sequence.Next(p1, p2));
// you can also verify how many times the sequence was called
Assert.AreEqual(5, sequence.CallCount);
```
## Full Example
Let's look at how we can unit test the following class (`LocationManager`) using SimpleStubs.
```csharp
using System.Threading.Tasks;
namespace HelloApp
{
public class LocationManager
{
private readonly ILocationService _locationService;
public LocationManager(ILocationService locationService)
{
_locationService = locationService;
}
/// <returns>Current Location or null if the location could not be retrieved</returns>
public async Task<Location> GetCurrentLocation()
{
try
{
string location = await _locationService.GetLocation();
var ss = location.Split('/');
return new Location(ss[0], ss[1]);
}
catch (LocationServiceUnavailableException)
{
return null;
}
}
/// <returns>The current country code (e.g. US, CA) or null if the country code could not be retrieved</returns>
public async Task<string> GetCurrentCountryCode()
{
try
{
Location location = await GetCurrentLocation();
string loc = $"{location.Country}/{location.City}";
return await _locationService.GetCountryCode(loc);
}
catch (LocationServiceUnavailableException)
{
return null;
}
}
}
}
```
The `ILocationService` interface is as follows:
```csharp
using System;
using System.Threading.Tasks;
namespace HelloApp
{
public interface ILocationService
{
/// <returns>
/// the location in the format Country/City
/// </returns>
/// <exception cref="LocationServiceUnavailableException"></exception>
Task<string> GetLocation();
/// <returns>the country code of the given location</returns>
/// <exception cref="LocationServiceUnavailableException"></exception>
Task<string> GetCountryCode(string location);
}
}
```
SimpleStubs will automatically generate a stub for `ILocationService` called StubILocationService. The following tests show how the stub can be used to unit test the `LocationManager` class:
```csharp
[TestMethod]
public async Task TestGetCurrentLocation()
{
StubILocationService locationServiceStub = new StubILocationService
{
GetLocation = () => Task.FromResult("Canada/Vancouver")
};
LocationManager locationManager = new LocationManager(locationServiceStub);
Location location = await locationManager.GetCurrentLocation();
Assert.AreEqual("Canada", location.Country);
Assert.AreEqual("Vancouver", location.City);
Assert.AreEqual(1, locationServiceStub.ILocationService_GetLocation_CallCount);
}
[TestMethod]
public async Task TestThatGetCurrentLocationReturnsNullIfLocationServiceIsUnavailable()
{
StubILocationService locationServiceStub = new StubILocationService
{
GetLocation = () =>
{
throw new LocationServiceUnavailableException();
}
};
LocationManager locationManager = new LocationManager(locationServiceStub);
Assert.IsNull(await locationManager.GetCurrentLocation());
Assert.AreEqual(1, locationServiceStub.ILocationService_GetLocation_CallCount);
}
[TestMethod]
public async Task TestGetCurrentCountryCode()
{
StubILocationService locationServiceStub = new StubILocationService
{
GetLocation = () => Task.FromResult("Canada/Vancouver"),
GetCountryCode_String = location => Task.FromResult("CA")
};
LocationManager locationManager = new LocationManager(locationServiceStub);
Assert.AreEqual("CA", await locationManager.GetCurrentCountryCode());
Assert.AreEqual(1, locationServiceStub.ILocationService_GetCountryCode_String_CallCount);
}
```
## Configuration
SimpleStubs also supports an optional configuration file that can be added to the root of your test project. The configuration file (named `SimpleStubs.json`) has the following structure:
@ -256,11 +145,9 @@ The configuration file allows you to instruct SimpleStubs to omit creating stubs
It's also possible to instruct SimpleStubs to create stubs for internal interfaces (by default only public interfaces are stubbed) as shown in the configuration sample above.
## Current limitations
* Methods signatures with pointers are not supported.
* Generic methods are not supported (but generic interfaces are).
* Only interfaces are stubbed.
* Generic constrains are not supported.
## What if some stubs don't compile?
Exclude the interface that is causing the problem (using the `SimpleStubs.json` configuration file) and report the problem.
Exclude the interface that is causing the problem (using the `SimpleStubs.json` configuration file) and report the problem by opening an issue.

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

@ -2,7 +2,7 @@
<package >
<metadata>
<id>Etg.SimpleStubs</id>
<version>1.0.3</version>
<version>2.0.0</version>
<title>SimpleStubs mocking framework</title>
<authors>Microsoft Studios (BigPark)</authors>
<owners>Microsoft Studios (BigPark)</owners>

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

@ -9,90 +9,94 @@ namespace Etg.SimpleStubs.CodeGen
{
using SF = SyntaxFactory;
class EventStubber : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol, INamedTypeSymbol stubbedInterface)
{
// only handle EventAdd and ignore EventRemove because we only need to stub the event once
if (!methodSymbol.IsEventAdd())
{
return classDclr;
}
internal class EventStubber : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface)
{
// only handle EventAdd and ignore EventRemove because we only need to stub the event once
if (!methodSymbol.IsEventAdd())
{
return classDclr;
}
// add the event implementation to the stub
IEventSymbol eventSymbol = (IEventSymbol)methodSymbol.AssociatedSymbol;
EventFieldDeclarationSyntax eventDclr = ToEventDclr(eventSymbol);
classDclr = classDclr.AddMembers(eventDclr);
// add the event implementation to the stub
IEventSymbol eventSymbol = (IEventSymbol) methodSymbol.AssociatedSymbol;
EventFieldDeclarationSyntax eventDclr = ToEventDclr(eventSymbol);
classDclr = classDclr.AddMembers(eventDclr);
string eventName = eventSymbol.Name;
ParameterSyntax[] parameters = GetEventParameters(eventSymbol);
string onEventArgs = "sender";
string eventTriggerArgs = "sender";
if (parameters.Count() == 2)
{
onEventArgs += ", args";
eventTriggerArgs += ", args";
}
else if (parameters.Count() == 1)
{
onEventArgs += ", null";
}
string eventName = eventSymbol.Name;
ParameterSyntax[] parameters = GetEventParameters(eventSymbol);
string onEventArgs = "sender";
string eventTriggerArgs = "sender";
if (parameters.Count() == 2)
{
onEventArgs += ", args";
eventTriggerArgs += ", args";
}
else if (parameters.Count() == 1)
{
onEventArgs += ", null";
}
string eventType = GetEventType(eventSymbol);
string onEventMethodName = "On_" + eventName;
string eventType = GetEventType(eventSymbol);
string onEventMethodName = "On_" + eventName;
// Create OnEvent method
MethodDeclarationSyntax onEventMethodDclr = SF.MethodDeclaration(SF.ParseTypeName("void"), onEventMethodName)
.AddModifiers(SF.Token(SyntaxKind.ProtectedKeyword))
.AddParameterListParameters(parameters)
.WithBody(SF.Block(
SF.ParseStatement($"{eventType} handler = {eventName};\n"),
SF.ParseStatement($"if (handler != null) {{ handler({onEventArgs}); }}\n")
));
// Create OnEvent method
MethodDeclarationSyntax onEventMethodDclr = SF.MethodDeclaration(SF.ParseTypeName("void"), onEventMethodName)
.AddModifiers(SF.Token(SyntaxKind.ProtectedKeyword))
.AddParameterListParameters(parameters)
.WithBody(SF.Block(
SF.ParseStatement($"{eventType} handler = {eventName};\n"),
SF.ParseStatement($"if (handler != null) {{ handler({onEventArgs}); }}\n")
));
classDclr = classDclr.AddMembers(onEventMethodDclr);
classDclr = classDclr.AddMembers(onEventMethodDclr);
// Create event trigger method
string eventTriggerMethodName = eventName + "_Raise";
MethodDeclarationSyntax eventTriggerMethod = SF.MethodDeclaration(SF.ParseTypeName("void"), eventTriggerMethodName)
.AddModifiers(SF.Token(SyntaxKind.PublicKeyword))
.AddParameterListParameters(parameters)
.WithBody(SF.Block(
SF.ParseStatement($"{onEventMethodName}({eventTriggerArgs});\n")
));
classDclr = classDclr.AddMembers(eventTriggerMethod);
// Create event trigger method
string eventTriggerMethodName = eventName + "_Raise";
MethodDeclarationSyntax eventTriggerMethod = SF.MethodDeclaration(SF.ParseTypeName("void"),
eventTriggerMethodName)
.AddModifiers(SF.Token(SyntaxKind.PublicKeyword))
.AddParameterListParameters(parameters)
.WithBody(SF.Block(
SF.ParseStatement($"{onEventMethodName}({eventTriggerArgs});\n")
));
classDclr = classDclr.AddMembers(eventTriggerMethod);
return classDclr;
}
return classDclr;
}
private static ParameterSyntax[] GetEventParameters(IEventSymbol eventSymbol)
{
List<ParameterSyntax> parameters = new List<ParameterSyntax>
{
SF.Parameter(SF.Identifier("sender")).WithType(SF.ParseTypeName("object"))
};
INamedTypeSymbol type = (INamedTypeSymbol)(eventSymbol.Type);
if (type.TypeArguments.Any())
{
parameters.Add(SF.Parameter(SF.Identifier("args")).WithType(SF.ParseTypeName(type.TypeArguments[0].Name)));
}
private static ParameterSyntax[] GetEventParameters(IEventSymbol eventSymbol)
{
List<ParameterSyntax> parameters = new List<ParameterSyntax>
{
SF.Parameter(SF.Identifier("sender")).WithType(SF.ParseTypeName("object"))
};
INamedTypeSymbol type = (INamedTypeSymbol) (eventSymbol.Type);
if (type.TypeArguments.Any())
{
parameters.Add(SF.Parameter(SF.Identifier("args"))
.WithType(SF.ParseTypeName(type.TypeArguments[0].Name)));
}
return parameters.ToArray();
}
return parameters.ToArray();
}
private static EventFieldDeclarationSyntax ToEventDclr(IEventSymbol eventSymbol)
{
string eventName = eventSymbol.Name;
string eventType = GetEventType(eventSymbol);
EventFieldDeclarationSyntax eventDclr = SF.EventFieldDeclaration(
SF.VariableDeclaration(SF.IdentifierName(eventType),
SF.SeparatedList(new[] { SF.VariableDeclarator(eventName) }))).AddModifiers(SF.Token(SyntaxKind.PublicKeyword));
return eventDclr;
}
private static EventFieldDeclarationSyntax ToEventDclr(IEventSymbol eventSymbol)
{
string eventName = eventSymbol.Name;
string eventType = GetEventType(eventSymbol);
EventFieldDeclarationSyntax eventDclr = SF.EventFieldDeclaration(
SF.VariableDeclaration(SF.IdentifierName(eventType),
SF.SeparatedList(new[] {SF.VariableDeclarator(eventName)})))
.AddModifiers(SF.Token(SyntaxKind.PublicKeyword));
return eventDclr;
}
private static string GetEventType(IEventSymbol eventSymbol)
{
return eventSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
}
private static string GetEventType(IEventSymbol eventSymbol)
{
return eventSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
}
}

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

@ -3,8 +3,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Etg.SimpleStubs.CodeGen
{
interface IInterfaceStubber
internal interface IInterfaceStubber
{
CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDeclarationSyntax interfaceDclr, SemanticModel semanticModel);
CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDeclarationSyntax interfaceDclr,
SemanticModel semanticModel);
}
}

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

@ -3,8 +3,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Etg.SimpleStubs.CodeGen
{
interface IMethodStubber
internal interface IMethodStubber
{
ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol, INamedTypeSymbol stubbedInterface);
ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface);
}
}

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

@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Etg.SimpleStubs.CodeGen.CodeGen
{
interface IProjectStubber
internal interface IProjectStubber
{
Task<StubProjectResult> StubProject(Project project, CompilationUnitSyntax cu);
}

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

@ -9,15 +9,16 @@ using Etg.SimpleStubs.CodeGen.Utils;
namespace Etg.SimpleStubs.CodeGen
{
class InterfaceStubber : IInterfaceStubber
internal class InterfaceStubber : IInterfaceStubber
{
private readonly IEnumerable<IMethodStubber> _methodStubbers;
public InterfaceStubber(IEnumerable<IMethodStubber> methodStubbers)
{
_methodStubbers = new List<IMethodStubber>(methodStubbers);
}
public CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDeclarationSyntax interfaceDclr,
public CompilationUnitSyntax StubInterface(CompilationUnitSyntax cu, InterfaceDeclarationSyntax interfaceDclr,
SemanticModel semanticModel)
{
INamedTypeSymbol interfaceType = semanticModel.GetDeclaredSymbol(interfaceDclr);
@ -28,12 +29,24 @@ namespace Etg.SimpleStubs.CodeGen
.AddModifiers(SF.Token(RoslynUtils.GetVisibilityKeyword(interfaceType))
//,SF.Token(SyntaxKind.PartialKeyword)
)
.WithBaseList(RoslynUtils.BaseList(interfaceName))
.AddAttributeLists(AttributeListList(Attribute("CompilerGenerated")).ToArray());
.WithBaseList(RoslynUtils.BaseList(interfaceName))
.AddAttributeLists(AttributeListList(Attribute("CompilerGenerated")).ToArray());
classDclr = classDclr.AddMembers(
SF.FieldDeclaration(
SF.VariableDeclaration(SF.ParseTypeName("Dictionary<string, object>"),
SF.SeparatedList(new[]
{
SF.VariableDeclarator(SF.Identifier("_stubs"), null,
SF.EqualsValueClause(SF.ParseExpression("new Dictionary<string, object>()")))
})))
.AddModifiers(SF.Token(SyntaxKind.PrivateKeyword), SF.Token(SyntaxKind.ReadOnlyKeyword)));
List<IMethodSymbol> methodsToStub = RoslynUtils.GetAllMethods(interfaceType);
foreach (IMethodSymbol methodSymbol in methodsToStub)
{
foreach(IMethodStubber methodStubber in _methodStubbers)
foreach (IMethodStubber methodStubber in _methodStubbers)
{
classDclr = methodStubber.StubMethod(classDclr, methodSymbol, interfaceType);
}
@ -58,29 +71,29 @@ namespace Etg.SimpleStubs.CodeGen
return namespaceNode;
}
private static SyntaxList<AttributeListSyntax> AttributeListList(params AttributeSyntax[] attributes)
{
var list = new SyntaxList<AttributeListSyntax>();
foreach (AttributeSyntax attributeSyntax in attributes)
{
list = list.Add(AttributeList(attributeSyntax));
}
return list;
}
private static SyntaxList<AttributeListSyntax> AttributeListList(params AttributeSyntax[] attributes)
{
var list = new SyntaxList<AttributeListSyntax>();
foreach (AttributeSyntax attributeSyntax in attributes)
{
list = list.Add(AttributeList(attributeSyntax));
}
return list;
}
private static AttributeListSyntax AttributeList(params AttributeSyntax[] attributes)
{
SeparatedSyntaxList<AttributeSyntax> separatedList = SF.SeparatedList<AttributeSyntax>();
foreach (var attributeSyntax in attributes)
{
separatedList = separatedList.Add(attributeSyntax);
}
return SF.AttributeList(separatedList);
}
private static AttributeListSyntax AttributeList(params AttributeSyntax[] attributes)
{
SeparatedSyntaxList<AttributeSyntax> separatedList = SF.SeparatedList<AttributeSyntax>();
foreach (var attributeSyntax in attributes)
{
separatedList = separatedList.Add(attributeSyntax);
}
return SF.AttributeList(separatedList);
}
private static AttributeSyntax Attribute(string attributeName)
{
return SF.Attribute(SF.IdentifierName(attributeName));
}
}
private static AttributeSyntax Attribute(string attributeName)
{
return SF.Attribute(SF.IdentifierName(attributeName));
}
}
}

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

@ -7,11 +7,12 @@ using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Etg.SimpleStubs.CodeGen
{
class OrdinaryMethodStubber : IMethodStubber
internal class OrdinaryMethodStubber : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol, INamedTypeSymbol stubbedInterface)
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface)
{
if(!methodSymbol.IsOrdinaryMethod())
if (!methodSymbol.IsOrdinaryMethod())
{
return classDclr;
}
@ -22,35 +23,32 @@ namespace Etg.SimpleStubs.CodeGen
RoslynUtils.GetMethodParameterSyntaxList(methodSymbol).ToArray()));
methodDclr = methodDclr.WithSemicolonToken(SF.Token(SyntaxKind.None))
.WithExplicitInterfaceSpecifier(
SF.ExplicitInterfaceSpecifier(
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
if (methodSymbol.IsGenericMethod)
{
StatementSyntax stmtSyntax;
if (methodSymbol.ReturnsVoid)
{
stmtSyntax = SF.ParseStatement("\n");
}
else
{
stmtSyntax = SF.ParseStatement($"return default({methodSymbol.ReturnType.GetFullyQualifiedName()});\n");
}
SF.ExplicitInterfaceSpecifier(
SF.IdentifierName(methodSymbol.GetContainingInterfaceGenericQualifiedName())));
classDclr = classDclr.AddMembers(methodDclr.WithBody(SF.Block(stmtSyntax)));
}
else
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string parameters = string.Join(", ", methodSymbol.Parameters.Select(p =>
{
string delegatePropertyName = NamingUtils.GetDelegatePropertyName(methodSymbol, stubbedInterface);
string callDelegateStmt = $"{delegatePropertyName}({string.Join(", ", methodSymbol.Parameters.Select(p => p.Name))});\n";
if (!methodSymbol.ReturnsVoid)
if (p.RefKind == RefKind.Out)
{
callDelegateStmt = callDelegateStmt.Insert(0, "return ");
return $"out {p.Name}";
}
if (p.RefKind == RefKind.Ref)
{
return $"ref {p.Name}";
}
return p.Name;
}));
classDclr = classDclr.AddMembers(
methodDclr.WithBody(SF.Block(SF.ParseStatement(callDelegateStmt))));
string callDelegateStmt = StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, parameters);
if (!methodSymbol.ReturnsVoid)
{
callDelegateStmt = callDelegateStmt.Insert(0, "return ");
}
classDclr = classDclr.AddMembers(
methodDclr.WithBody(SF.Block(SF.ParseStatement(callDelegateStmt))));
return classDclr;
}
}

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

@ -11,10 +11,11 @@ using System.Threading.Tasks;
namespace Etg.SimpleStubs.CodeGen.CodeGen
{
class ProjectStubber : IProjectStubber
internal class ProjectStubber : IProjectStubber
{
private readonly IInterfaceStubber _interfaceStubber;
private readonly SimpleStubsConfig _config;
public ProjectStubber(IInterfaceStubber interfaceStubber, SimpleStubsConfig config)
{
_interfaceStubber = interfaceStubber;
@ -28,7 +29,11 @@ namespace Etg.SimpleStubs.CodeGen.CodeGen
{
SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
SemanticModel semanticModel = await document.GetSemanticModelAsync();
IEnumerable<InterfaceDeclarationSyntax> interfaces = syntaxTree.GetRoot().DescendantNodes().OfType<InterfaceDeclarationSyntax>().Where(i => SatisfiesVisibilityConstraints(i));
IEnumerable<InterfaceDeclarationSyntax> interfaces =
syntaxTree.GetRoot()
.DescendantNodes()
.OfType<InterfaceDeclarationSyntax>()
.Where(i => SatisfiesVisibilityConstraints(i));
if (!interfaces.Any())
{
continue;

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

@ -9,19 +9,22 @@ namespace Etg.SimpleStubs.CodeGen
using System.Linq;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
class PropertyStubber : IMethodStubber
internal class PropertyStubber : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol, INamedTypeSymbol stubbedInterface)
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface)
{
if (!methodSymbol.IsPropertyAccessor())
{
return classDclr;
}
string delegatePropertyName = NamingUtils.GetDelegatePropertyName(methodSymbol, stubbedInterface);
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string propName = methodSymbol.AssociatedSymbol.Name;
string propType = ((IPropertySymbol)methodSymbol.AssociatedSymbol).Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
string propType =
((IPropertySymbol) methodSymbol.AssociatedSymbol).Type.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
var propDclr = GetPropDclr(classDclr, propName);
if (propDclr == null)
{
@ -33,15 +36,19 @@ namespace Etg.SimpleStubs.CodeGen
if (methodSymbol.IsPropertyGetter())
{
var accessorDclr = SF.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, SF.Block(
SF.List(new[] {
SF.ReturnStatement(SF.IdentifierName(delegatePropertyName + "()")) })));
SF.List(new[]
{
SF.ParseStatement("return " + StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, ""))
})));
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
}
else if (methodSymbol.IsPropertySetter())
{
var accessorDclr = SF.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, SF.Block(
SF.List(new[] {
SF.ParseStatement($"{delegatePropertyName}(value);\n") })));
SF.List(new[]
{
SF.ParseStatement(StubbingUtils.GenerateInvokeDelegateStmt(delegateTypeName, "value"))
})));
propDclr = propDclr.AddAccessorListAccessors(accessorDclr);
}
@ -63,7 +70,10 @@ namespace Etg.SimpleStubs.CodeGen
private static PropertyDeclarationSyntax GetPropDclr(ClassDeclarationSyntax classDclr, string propName)
{
return classDclr.DescendantNodes().OfType<PropertyDeclarationSyntax>().FirstOrDefault(p => p.Identifier.Text == propName);
return
classDclr.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.FirstOrDefault(p => p.Identifier.Text == propName);
}
}
}

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

@ -14,8 +14,9 @@ namespace Etg.SimpleStubs.CodeGen
{
internal class SimpleStubsGenerator
{
IProjectStubber _projectStubber;
SimpleStubsConfig _config;
private IProjectStubber _projectStubber;
private SimpleStubsConfig _config;
public SimpleStubsGenerator(IProjectStubber projectStubber, SimpleStubsConfig config)
{
_projectStubber = projectStubber;

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

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Etg.SimpleStubs.CodeGen.CodeGen
{
class StubProjectResult
internal class StubProjectResult
{
public StubProjectResult(CompilationUnitSyntax cu, IEnumerable<string> usings)
{
@ -11,14 +11,8 @@ namespace Etg.SimpleStubs.CodeGen.CodeGen
Usings = usings;
}
public CompilationUnitSyntax CompilationUnit
{
get;
}
public CompilationUnitSyntax CompilationUnit { get; }
public IEnumerable<string> Usings
{
get;
}
public IEnumerable<string> Usings { get; }
}
}

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

@ -0,0 +1,57 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Etg.SimpleStubs.CodeGen.Utils;
namespace Etg.SimpleStubs.CodeGen
{
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Generic;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
internal class StubbingDelegateGenerator : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface)
{
if (methodSymbol.IsPropertyAccessor() || methodSymbol.IsOrdinaryMethod())
{
string delegateTypeName = NamingUtils.GetDelegateTypeName(methodSymbol, stubbedInterface);
string setupMethodName = NamingUtils.GetSetupMethodName(methodSymbol);
DelegateDeclarationSyntax delegateDclr = GenerateDelegateDclr(methodSymbol, delegateTypeName,
stubbedInterface);
MethodDeclarationSyntax propDclr = GenerateSetupMethod(setupMethodName, delegateTypeName,
stubbedInterface, classDclr);
classDclr = classDclr.AddMembers(delegateDclr, propDclr);
}
return classDclr;
}
private static MethodDeclarationSyntax GenerateSetupMethod(string setupMethodName, string delegateTypeName,
INamedTypeSymbol stubbedInterface,
ClassDeclarationSyntax stub)
{
SyntaxKind visibility = RoslynUtils.GetVisibilityKeyword(stubbedInterface);
return SF.MethodDeclaration(SF.ParseTypeName(stub.Identifier.Text), setupMethodName)
.AddModifiers(SF.Token(visibility)).WithSemicolonToken(SF.Token(SyntaxKind.SemicolonToken))
.AddParameterListParameters(
SF.Parameter(SF.Identifier("del")).WithType(SF.ParseTypeName(delegateTypeName)))
.WithBody(SF.Block(
SF.ParseStatement($"_stubs[nameof({delegateTypeName})] = del;\n"),
SF.ParseStatement("return this;\n")
))
.WithSemicolonToken(SF.Token(SyntaxKind.None));
}
private static DelegateDeclarationSyntax GenerateDelegateDclr(IMethodSymbol methodSymbol, string delegateName,
INamedTypeSymbol stubbedInterface)
{
SyntaxKind visibility = RoslynUtils.GetVisibilityKeyword(stubbedInterface);
List<ParameterSyntax> paramsSyntaxList = RoslynUtils.GetMethodParameterSyntaxList(methodSymbol);
return SF.DelegateDeclaration(SF.ParseTypeName(methodSymbol.ReturnType.GetFullyQualifiedName()),
delegateName)
.AddModifiers(SF.Token(visibility)).AddParameterListParameters(paramsSyntaxList.ToArray());
}
}
}

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

@ -1,52 +0,0 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Etg.SimpleStubs.CodeGen.Utils;
namespace Etg.SimpleStubs.CodeGen
{
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
class StubbingPropertiesGenerator : IMethodStubber
{
public ClassDeclarationSyntax StubMethod(ClassDeclarationSyntax classDclr, IMethodSymbol methodSymbol,
INamedTypeSymbol stubbedInterface)
{
if (methodSymbol.IsPropertyAccessor() || methodSymbol.IsOrdinaryMethod())
{
if (!methodSymbol.IsGenericMethod)
{
string delegatePropertyName = NamingUtils.GetDelegatePropertyName(methodSymbol, stubbedInterface);
string delegateTypeName = NamingUtils.GetDelegateTypeName(delegatePropertyName);
DelegateDeclarationSyntax delegateDclr = GenerateDelegateDclr(methodSymbol, delegateTypeName, stubbedInterface);
PropertyDeclarationSyntax propDclr = GenerateDelegatePropDclr(
delegatePropertyName, delegateTypeName, stubbedInterface);
classDclr = classDclr.AddMembers(delegateDclr, propDclr);
}
}
return classDclr;
}
private static PropertyDeclarationSyntax GenerateDelegatePropDclr(string delegatePropertyName,
string delegateName, INamedTypeSymbol stubbedInterface)
{
SyntaxKind visibility = RoslynUtils.GetVisibilityKeyword(stubbedInterface);
return SF.PropertyDeclaration(SF.ParseTypeName(delegateName), delegatePropertyName)
.AddModifiers(SF.Token(visibility)).WithSemicolonToken(SF.Token(SyntaxKind.SemicolonToken));
}
private static DelegateDeclarationSyntax GenerateDelegateDclr(IMethodSymbol methodSymbol, string delegateName,
INamedTypeSymbol stubbedInterface)
{
SyntaxKind visibility = RoslynUtils.GetVisibilityKeyword(stubbedInterface);
List<ParameterSyntax> paramsSyntaxList = RoslynUtils.GetMethodParameterSyntaxList(methodSymbol);
return SF.DelegateDeclaration(SF.ParseTypeName(methodSymbol.ReturnType.GetFullyQualifiedName()), delegateName)
.AddModifiers(SF.Token(visibility)).AddParameterListParameters(paramsSyntaxList.ToArray());
}
}
}

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

@ -4,7 +4,7 @@ using System.IO;
namespace Etg.SimpleStubs.CodeGen.Config
{
class ConfigLoader
internal class ConfigLoader
{
public SimpleStubsConfig LoadConfig(string configFilePath)
{
@ -13,7 +13,7 @@ namespace Etg.SimpleStubs.CodeGen.Config
return JsonConvert.DeserializeObject<SimpleStubsConfig>(File.ReadAllText(configFilePath));
}
return new SimpleStubsConfig(new string[]{}, new string[]{}, false);
return new SimpleStubsConfig(new string[] {}, new string[] {}, false);
}
}
}

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

@ -2,9 +2,9 @@
namespace Etg.SimpleStubs.CodeGen.Config
{
class SimpleStubsConfig
internal class SimpleStubsConfig
{
public SimpleStubsConfig(IEnumerable<string> ignoredProjects,
public SimpleStubsConfig(IEnumerable<string> ignoredProjects,
IEnumerable<string> ignoredInterfaces,
bool stubInternalInterfaces)
{
@ -13,19 +13,10 @@ namespace Etg.SimpleStubs.CodeGen.Config
StubInternalInterfaces = stubInternalInterfaces;
}
public ISet<string> IgnoredProjects
{
get;
}
public ISet<string> IgnoredProjects { get; }
public ISet<string> IgnoredInterfaces
{
get;
}
public ISet<string> IgnoredInterfaces { get; }
public bool StubInternalInterfaces
{
get;
}
public bool StubInternalInterfaces { get; }
}
}

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

@ -5,7 +5,7 @@ using System.IO;
namespace Etg.SimpleStubs.CodeGen.DI
{
class DiModule
internal class DiModule
{
private readonly IContainer _container;
@ -26,11 +26,13 @@ namespace Etg.SimpleStubs.CodeGen.DI
cb.Register((c) =>
{
IInterfaceStubber interfaceStubber = new InterfaceStubber(
new IMethodStubber[] {
new OrdinaryMethodStubber(),
new EventStubber(),
new PropertyStubber(),
new StubbingPropertiesGenerator() });
new IMethodStubber[]
{
new OrdinaryMethodStubber(),
new EventStubber(),
new PropertyStubber(),
new StubbingDelegateGenerator()
});
return interfaceStubber;
}).As<IInterfaceStubber>().SingleInstance();
@ -42,4 +44,4 @@ namespace Etg.SimpleStubs.CodeGen.DI
public SimpleStubsGenerator StubsGenerator => _container.Resolve<SimpleStubsGenerator>();
}
}
}

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

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SimpleStubs")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3e9c520a-94cf-46d0-864b-4293d439c92a")]
// Version information for an assembly consists of the following four values:
@ -32,6 +35,6 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.1.0")]
[assembly: InternalsVisibleTo("TestClassLibraryTest")]
[assembly: AssemblyVersion("0.0.1.0")]
[assembly: InternalsVisibleTo("TestClassLibraryTest")]

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

@ -103,6 +103,7 @@
<Compile Include="CodeGen\InterfaceStubber.cs" />
<Compile Include="CodeGen\IProjectStubber.cs" />
<Compile Include="CodeGen\ProjectStubber.cs" />
<Compile Include="Utils\StubbingUtils.cs" />
<Compile Include="CodeGen\StubProjectResult.cs" />
<Compile Include="Config\SimpleStubsConfig.cs" />
<Compile Include="Config\ConfigLoader.cs" />
@ -113,7 +114,7 @@
<Compile Include="CodeGen\IMethodStubber.cs" />
<Compile Include="CodeGen\OrdinaryMethodStubber.cs" />
<Compile Include="CodeGen\PropertyStubber.cs" />
<Compile Include="CodeGen\StubbingPropertiesGenerator.cs" />
<Compile Include="CodeGen\StubbingDelegateGenerator.cs" />
<Compile Include="CodeGen\SimpleStubsGenerator.cs" />
<Compile Include="Utils\NamingUtils.cs" />
<Compile Include="Utils\RoslynExtensions.cs" />

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

@ -10,24 +10,16 @@ namespace Etg.SimpleStubs.CodeGen.Tasks
public class GenerateStubsTask : Microsoft.Build.Utilities.AppDomainIsolatedTask
{
[Required]
public string OutputPath
{
get;
set;
}
public string OutputPath { get; set; }
[Required]
public string ProjectPath
{
get;
set;
}
public string ProjectPath { get; set; }
public override bool Execute()
{
try
{
LogMessage("Generating stubs");
LogMessage("Generating stubs");
DiModule diModule = new DiModule(ProjectPath, OutputPath);
File.WriteAllText(OutputPath, diModule.StubsGenerator.GenerateStubs(ProjectPath).Result);
return true;

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

@ -4,45 +4,62 @@ using System.Text;
namespace Etg.SimpleStubs.CodeGen.Utils
{
class NamingUtils
internal class NamingUtils
{
public string GetInterfaceStubName(string interfaceName)
{
return "Stub" + interfaceName;
}
public static string GetDelegatePropertyName(IMethodSymbol methodSymbol, INamedTypeSymbol targetInterface)
public static string GetSetupMethodName(IMethodSymbol methodSymbol)
{
if (methodSymbol.IsPropertyGetter())
{
return methodSymbol.Name.Substring(4) + "_Get";
}
if (methodSymbol.IsPropertySetter())
{
return methodSymbol.Name.Substring(4) + "_Set";
}
return methodSymbol.GetGenericName();
}
public static string GetDelegateTypeName(IMethodSymbol methodSymbol, INamedTypeSymbol targetInterface)
{
string methodName = methodSymbol.Name;
if (methodSymbol.IsPropertyGetter())
{
methodName = methodName.Substring(4) + "_Get";
}
else if (methodSymbol.IsPropertySetter())
{
methodName = methodName.Substring(4) + "_Set";
}
string methodName = methodSymbol.Name;
if (methodSymbol.IsPropertyGetter())
{
methodName = methodName.Substring(4) + "_Get";
}
else if (methodSymbol.IsPropertySetter())
{
methodName = methodName.Substring(4) + "_Set";
}
// only prefix inherited members
if (targetInterface.GetGenericName() != methodSymbol.ContainingSymbol.GetGenericName())
{
methodName = SerializeName(methodSymbol.ContainingSymbol) + "_" + methodName;
}
// only prefix inherited members
if (targetInterface.GetGenericName() != methodSymbol.ContainingSymbol.GetGenericName())
{
methodName = SerializeName(methodSymbol.ContainingSymbol) + "_" + methodName;
}
if(methodSymbol.IsOrdinaryMethod())
{
if (methodSymbol.Parameters.Any())
{
methodName = methodName + "_" + string.Join("_", methodSymbol.Parameters.Select(SerializeName));
}
}
return methodName;
if (methodSymbol.IsOrdinaryMethod())
{
if (methodSymbol.Parameters.Any())
{
methodName = methodName + "_" + string.Join("_", methodSymbol.Parameters.Select(SerializeName));
}
}
methodName += "_Delegate";
if (methodSymbol.IsGenericMethod)
{
methodName =
$"{methodName}<{string.Join(",", methodSymbol.TypeParameters.Select(symbol => symbol.Name))}>";
}
return methodName;
}
public static string GetDelegateTypeName(string delegatePropertyName)
{
return delegatePropertyName + "_Delegate";
}
public static string SerializeName(ISymbol param)
{
@ -66,7 +83,6 @@ namespace Etg.SimpleStubs.CodeGen.Utils
{
sb.Append(part.Symbol.Name);
}
break;
}
}
@ -79,4 +95,4 @@ namespace Etg.SimpleStubs.CodeGen.Utils
return "Stub" + interfaceName;
}
}
}
}

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

@ -7,10 +7,10 @@ using System.Collections.Generic;
namespace Etg.SimpleStubs.CodeGen.Utils
{
static class RoslynExtensions
internal static class RoslynExtensions
{
public static SymbolDisplayFormat QualifiedFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
public static bool IsEvent(this IMethodSymbol methodSymbol)
{
@ -69,7 +69,10 @@ namespace Etg.SimpleStubs.CodeGen.Utils
public static string GetQualifiedName(this ITypeSymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries).Last();
return
symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
.Split(new[] {"::"}, StringSplitOptions.RemoveEmptyEntries)
.Last();
}
public static string GetMinimallyQualifiedName(this ITypeSymbol symbol)
@ -84,7 +87,9 @@ namespace Etg.SimpleStubs.CodeGen.Utils
public static bool IsPublic(this TypeDeclarationSyntax typeDclr)
{
return typeDclr.Modifiers.Any(modifier => modifier.RawKind.Equals(SyntaxFactory.Token(SyntaxKind.PublicKeyword).RawKind));
return
typeDclr.Modifiers.Any(
modifier => modifier.RawKind.Equals(SyntaxFactory.Token(SyntaxKind.PublicKeyword).RawKind));
}
public static bool IsInternal(this TypeDeclarationSyntax typeDclr)
@ -97,6 +102,5 @@ namespace Etg.SimpleStubs.CodeGen.Utils
};
return !typeDclr.Modifiers.Any(modifier => nonInternalModifiers.Contains(modifier.RawKind));
}
}
}

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

@ -7,7 +7,7 @@ using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Etg.SimpleStubs.CodeGen.Utils
{
static class RoslynUtils
internal static class RoslynUtils
{
public static UsingDirectiveSyntax UsingDirective(string nameSpace)
{
@ -21,12 +21,15 @@ namespace Etg.SimpleStubs.CodeGen.Utils
public static ParameterSyntax CreateParameter(string type, string name)
{
return SF.Parameter(new SyntaxList<AttributeListSyntax>(), new SyntaxTokenList(), SF.IdentifierName(type), SF.Identifier(new SyntaxTriviaList().Add(SF.Space), name, new SyntaxTriviaList()), null);
return SF.Parameter(new SyntaxList<AttributeListSyntax>(), new SyntaxTokenList(), SF.IdentifierName(type),
SF.Identifier(new SyntaxTriviaList().Add(SF.Space), name, new SyntaxTriviaList()), null);
}
public static BaseListSyntax BaseList(params string[] names)
{
return SF.BaseList(SF.SeparatedList<BaseTypeSyntax>(names.Select(name => SF.SimpleBaseType(SF.IdentifierName(name)))));
return
SF.BaseList(
SF.SeparatedList<BaseTypeSyntax>(names.Select(name => SF.SimpleBaseType(SF.IdentifierName(name)))));
}
public static List<ParameterSyntax> GetMethodParameterSyntaxList(IMethodSymbol methodSymbol)
@ -34,7 +37,18 @@ namespace Etg.SimpleStubs.CodeGen.Utils
var paramsSyntaxList = new List<ParameterSyntax>();
foreach (IParameterSymbol param in methodSymbol.Parameters)
{
ParameterSyntax paramSyntax = SF.Parameter(SF.Identifier(param.Name)).WithType(SF.ParseTypeName(param.Type.GetFullyQualifiedName()));
ParameterSyntax paramSyntax = SF.Parameter(SF.Identifier(param.Name))
.WithType(SF.ParseTypeName(param.Type.GetFullyQualifiedName()));
if (param.RefKind == RefKind.Out)
{
paramSyntax = paramSyntax.WithModifiers(SyntaxTokenList.Create(SF.Token(SyntaxKind.OutKeyword)));
}
else if (param.RefKind == RefKind.Ref)
{
paramSyntax = paramSyntax.WithModifiers(SyntaxTokenList.Create(SF.Token(SyntaxKind.RefKeyword)));
}
paramsSyntaxList.Add(paramSyntax);
}
@ -65,7 +79,9 @@ namespace Etg.SimpleStubs.CodeGen.Utils
public static SyntaxKind GetVisibilityKeyword(ISymbol stubbedInterface)
{
return stubbedInterface.DeclaredAccessibility ==
Accessibility.Internal ? SyntaxKind.InternalKeyword : SyntaxKind.PublicKeyword;
Accessibility.Internal
? SyntaxKind.InternalKeyword
: SyntaxKind.PublicKeyword;
}
}
}

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

@ -0,0 +1,10 @@
namespace Etg.SimpleStubs.CodeGen.Utils
{
internal class StubbingUtils
{
public static string GenerateInvokeDelegateStmt(string delegateTypeName, string parameters)
{
return $"(({delegateTypeName})_stubs[nameof({delegateTypeName})]).Invoke({parameters});\n";
}
}
}

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

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="3.5.2" targetFramework="net461" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net461" />

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

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace TestClassLibrary
@ -20,7 +22,18 @@ namespace TestClassLibrary
event EventHandler<long> PhoneNumberChanged;
}
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
{
void Swap<T>(ref T v1, ref T v2);
}
public interface ITestInterface : IDisposable
{
@ -32,6 +45,8 @@ namespace TestClassLibrary
Task<List<int>> DoSomething(int parameter);
void SetDictionary(Dictionary<string, string> dict);
string Prop1 { get; }
string Prop2 { set; }
@ -40,9 +55,9 @@ namespace TestClassLibrary
event EventHandler<EventArgs> Changed;
event EventHandler OtherEvent;
event EventHandler OtherEvent;
List<T> GetGenericList<T, A>();
List<T> GetGenericList<T>();
void SetGenericValue<T>(T value);
}
@ -51,7 +66,7 @@ namespace TestClassLibrary
{
}
interface IInternalInterface
internal interface IInternalInterface
{
void DoSomethingInternal();
}
@ -61,4 +76,26 @@ namespace TestClassLibrary
T GetX();
}
}
public interface IInterfaceWithGenericMethod
{
T GetFoo<T>();
}
public class Stub : IInterfaceWithGenericMethod
{
private readonly Dictionary<string, object> _stubs = new Dictionary<string, object>();
public delegate T GetFooOfT_Delegate<T>();
public T GetFoo<T>()
{
return ((GetFooOfT_Delegate<T>) _stubs[nameof(GetFooOfT_Delegate<T>)]).Invoke();
}
public Stub SetupGetFooOfT<T>(GetFooOfT_Delegate<T> del)
{
_stubs[nameof(GetFooOfT_Delegate<T>)] = del;
return this;
}
}
}

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

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestClassLibrary")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ecbcbae6-949e-4e9c-84e1-614d97909b6c")]
// Version information for an assembly consists of the following four values:
@ -32,7 +35,7 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("TestClassLibraryTest")]
[assembly: InternalsVisibleTo("TestClassLibraryTest")]

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

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestClassLibraryTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using System.Runtime.InteropServices;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cb81f60f-1374-4b46-bb64-d848b5103a58")]
// Version information for an assembly consists of the following four values:
@ -32,5 +35,6 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

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

@ -18,15 +18,13 @@ namespace TestClassLibraryTest
long number = 6041234567;
string firstName = null;
string lastName = null;
var stub = new StubIPhoneBook
var stub = new StubIPhoneBook();
stub.GetContactPhoneNumber((fn, ln) =>
{
GetContactPhoneNumber_String_String = (fn, ln) =>
{
firstName = fn;
lastName = ln;
return number;
}
};
firstName = fn;
lastName = ln;
return number;
});
IPhoneBook phoneBook = stub;
long actualNumber = phoneBook.GetContactPhoneNumber("John", "Smith");
Assert.AreEqual(number, actualNumber);
@ -39,14 +37,11 @@ namespace TestClassLibraryTest
{
long myNumber = 6041234567;
long newNumber = 0;
var stub = new StubIPhoneBook
{
MyNumber_Get = () => myNumber,
MyNumber_Set = num =>
{
newNumber = num;
}
};
var stub = new StubIPhoneBook()
.MyNumber_Get(() => myNumber)
.MyNumber_Set(value => newNumber = value);
IPhoneBook phoneBook = stub;
Assert.AreEqual(myNumber, phoneBook.MyNumber);
phoneBook.MyNumber = 13;
@ -57,7 +52,7 @@ namespace TestClassLibraryTest
public void TestPropertyStubWithGetterOnly()
{
int contactsCount = 55;
var stub = new StubIPhoneBook { ContactsCount_Get = () => contactsCount };
var stub = new StubIPhoneBook().ContactsCount_Get(() => contactsCount);
IPhoneBook phoneBook = stub;
Assert.AreEqual(contactsCount, phoneBook.ContactsCount);
}
@ -71,10 +66,9 @@ namespace TestClassLibraryTest
stub.PhoneNumberChanged += (s, num) =>
{
sender = s;
newNumber = num;
newNumber = num;
}
;
;
stub.PhoneNumberChanged_Raise(this, 55);
Assert.AreEqual(55, newNumber);
Assert.AreEqual(this, sender);
@ -87,7 +81,7 @@ namespace TestClassLibraryTest
.Once((p1, p2) => 12345678) // first call
.Repeat((p1, p2) => 11122233, 2) // next two call
.Forever((p1, p2) => 22233556); // rest of the calls
var stub = new StubIPhoneBook { GetContactPhoneNumber_String_String = (p1, p2) => sequence.Next(p1, p2) };
var stub = new StubIPhoneBook().GetContactPhoneNumber((p1, p2) => sequence.Next(p1, p2));
IPhoneBook phoneBook = stub;
Assert.AreEqual(12345678, phoneBook.GetContactPhoneNumber("John", "Smith"));
Assert.AreEqual(11122233, phoneBook.GetContactPhoneNumber("John", "Smith"));
@ -99,15 +93,64 @@ namespace TestClassLibraryTest
Assert.AreEqual(6, sequence.CallCount);
}
[TestMethod]
public void TestGenericMethod()
{
int value = -1;
var stub = new StubIContainer()
.GetElement<int>(index => value)
.SetElement<int>((i, v) => { value = v; });
IContainer container = stub;
container.SetElement(0, 5);
Assert.AreEqual(5, container.GetElement<int>(1));
}
[TestMethod]
public void TestOutParameter()
{
object someObj = "test";
var stub = new StubIContainer()
.GetElement((int index, out object value) =>
{
value = someObj;
return true;
});
IContainer container = stub;
object result;
container.GetElement(0, out result);
Assert.AreEqual(someObj, result);
}
[TestMethod]
public void TestRefParameter()
{
var stub = new StubIRefUtils()
.Swap<int>((ref int v1, ref int v2) =>
{
int temp = v1;
v1 = v2;
v2 = temp;
});
int i1 = 1;
int i2 = 2;
((IRefUtils) stub).Swap<int>(ref i1, ref i2);
Assert.AreEqual(2, i1);
Assert.AreEqual(1, i2);
}
// this test is only used for debugging
[Ignore]
[TestMethod]
public async Task TestGenerateStubs()
{
string path = //@"C:\projects\JasperMain\Product\Jasper.Test\Jasper.Test.csproj";
@"..\..\TestClassLibraryTest.csproj";
//"..\\..\\SimpleStubsTest.csproj";
SimpleStubsGenerator stubsGenerator = new DiModule(path, @"..\..\Properties\SimpleStubs.generated.cs").StubsGenerator;
@"..\..\TestClassLibraryTest.csproj";
//"..\\..\\SimpleStubsTest.csproj";
SimpleStubsGenerator stubsGenerator =
new DiModule(path, @"..\..\Properties\SimpleStubs.generated.cs").StubsGenerator;
string stubs = await stubsGenerator.GenerateStubs(path);
File.WriteAllText(@"..\..\Properties\SimpleStubs.generated.cs", stubs);
}

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

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="3.5.2" targetFramework="net461" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net461" />