Last active
March 29, 2020 15:32
-
-
Save vova-lantsov-dev/318fe6af7489d99d80ba25a155e641de to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[SuppressMessage("ReSharper", "PossibleNullReferenceException")] | |
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] | |
private static IServiceProvider FakeServiceProviderWithSectionController | |
{ | |
get | |
{ | |
var rendererFactoryMock = new Mock<IRendererFactory>(); | |
var serviceProviderMock = new Mock<IServiceProvider>(); | |
var layoutProviderMock = new Mock<ILayoutProvider>(); | |
var eventBusMock = new Mock<IEventBus>(); | |
var types = typeof(ContentItem).Assembly.GetTypes(); | |
var imageInfoMock = new Mock<Graphics.ImageInfo>(); | |
var graphicsMock = new Mock<Graphics>(); | |
var nativeDocMock = new Mock<INativeDocument>(); | |
var docServiceMock = new Mock<IDocumentService>(); | |
var documentMock = new Mock<IDocument>(); | |
var outlineServiceMock = new Mock<IOutlineService>(); | |
var multilevelListServiceMock = new Mock<IMultilevelListService>(); | |
var occurredEvents = new List<string>(); | |
var snapshots = new List<LayoutSnapshot>(); | |
var listOfMocks = new List<Mock> | |
{ | |
rendererFactoryMock, serviceProviderMock, layoutProviderMock, eventBusMock, imageInfoMock, | |
graphicsMock, nativeDocMock, docServiceMock, documentMock, outlineServiceMock, | |
multilevelListServiceMock | |
}; | |
// Setup graphics mocks | |
graphicsMock | |
.Setup(graphics => graphics.LoadImage(It.IsAny<byte[]>())) | |
.Returns(() => imageInfoMock.Object); | |
graphicsMock | |
.Setup(graphics => graphics.LoadImage(It.IsAny<string>())) | |
.Returns(() => imageInfoMock.Object); | |
graphicsMock | |
.Setup(g => g.GetFontBBox(It.IsAny<Font>())) | |
.Returns(() => new Box(11.43f, -2.02f, 11.92f, -2.71f)); | |
graphicsMock | |
.Setup(g => g.MeasureText(It.IsAny<string>(), It.IsAny<Font>(), It.IsAny<float>(), It.IsAny<bool>(), | |
ref It.Ref<float>.IsAny)) | |
.Returns(20); | |
nativeDocMock | |
.Setup(d => d.CreateGraphics(It.IsAny<uint>())) | |
.Returns(() => graphicsMock.Object); | |
// Setup CurrentPage property that will be incremented on AddNewPage call | |
nativeDocMock | |
.SetupProperty(d => d.CurrentPage); | |
// Looking for all existing ContentItem-derived types | |
// f.e. Line, Image, StackFlow | |
foreach (var contentItemType in types.Where(t => t.IsSubclassOf(typeof(ContentItem)))) | |
{ | |
// Looking for the layouter derived from BaseLayouter<T> where T is contentItemType | |
// f.e. BaseLayouter<Line> | |
var layouterType = | |
types.FirstOrDefault(t => | |
t.IsSubclassOf(typeof(BaseLayouter<>).MakeGenericType(contentItemType))); | |
var layouterTypeToRegister = typeof(ILayouter<>).MakeGenericType(contentItemType); | |
// Create the Mock<ILayouter<T>> instance | |
var layouterMockType = typeof(Mock<>).MakeGenericType(layouterTypeToRegister); | |
dynamic layouterMock = (Mock) Activator.CreateInstance(layouterMockType); | |
{ | |
// dynamically create the following lambdas | |
// Expression<Func<ILayouter<T>, LayoutResult>> setupGetNextLayoutExpression = layouter => | |
// layouter.GetNextLayout(It.IsAny<object>(), It.IsAny<LayoutingContext>()); | |
// Expression<Action<ILayouter<T>> setupBeginPipelineExpression = layouter => | |
// layouter.BeginPipeline(It.IsAny<object>(), It.IsAny<LayoutingContext>(), default(CancellationToken)); | |
// 1. layouter | |
var layouterParameter = Expression.Parameter(layouterTypeToRegister, "layouter"); | |
// 2-1. layouter.GetNextLayout(It.IsAny<object>(), It.IsAny<LayoutingContext>()) | |
var callToGetNextLayout = Expression.Call(layouterParameter, | |
typeof(ILayouter).GetMethod("GetNextLayout", BindingFlags.Instance | BindingFlags.Public), | |
Expression.Call(typeof(It), "IsAny", new[] {typeof(object)}), | |
Expression.Call(typeof(It), "IsAny", new[] {typeof(LayoutingContext)})); | |
// 2-2. layouter.BeginPipeline(It.IsAny<object>(), It.IsAny<LayoutingContext>(), default(CancellationToken)) | |
var callToBeginPipeline = Expression.Call(layouterParameter, | |
typeof(ILayouter).GetMethod("BeginPipeline", BindingFlags.Instance | BindingFlags.Public), | |
Expression.Call(typeof(It), "IsAny", new[] {typeof(object)}), | |
Expression.Call(typeof(It), "IsAny", new[] {typeof(LayoutingContext)}), | |
Expression.Constant(default(CancellationToken), typeof(CancellationToken))); | |
// 3-1. layouter => layouter.GetNextLayout(It.IsAny<object>(), It.IsAny<LayoutingContext>()) | |
dynamic setupGetNextLayoutExpression = Expression.Lambda( | |
typeof(Func<,>).MakeGenericType(layouterTypeToRegister, typeof(LayoutResult)), | |
callToGetNextLayout, layouterParameter); | |
// 3-2. layouter => layouter.BeginPipeline( | |
// It.IsAny<object>(), It.IsAny<LayoutingContext>(), default(CancellationToken)) | |
dynamic setupBeginPipelineExpression = Expression.Lambda( | |
typeof(Action<>).MakeGenericType(layouterTypeToRegister), | |
callToBeginPipeline, layouterParameter); | |
var layouterInstance = new Lazy<ILayouter>(() => | |
(ILayouter) ActivatorUtilities.CreateInstance(serviceProviderMock.Object, layouterType)); | |
// dynamically add the tracing for calls of GetNextLayout() method | |
// 1. typeof(IReturns<ILayouter<T>, LayoutResult>) | |
var returnsType = | |
typeof(IReturns<,>).MakeGenericType(layouterTypeToRegister, typeof(LayoutResult)); | |
// 2. Call the Returns<object, LayoutingContext>(Func<object, LayoutingContext, LayoutResult> func) | |
returnsType | |
.GetMethods() | |
.First(m => m.IsGenericMethod && m.Name == "Returns" && | |
m.GetGenericArguments().Length == 2) | |
.MakeGenericMethod(typeof(object), typeof(LayoutingContext)) | |
.Invoke((object) layouterMock.Setup(setupGetNextLayoutExpression), new object[] | |
{ | |
(Func<object, LayoutingContext, LayoutResult>) ((item, context) => | |
{ | |
var layouter = layouterInstance.Value; | |
var result = layouter.GetNextLayout(item, context); | |
serviceProviderMock.Object | |
.GetRequiredService<IEventTracingController>() | |
.AppendMessage($"Got layout result with state: {result.State}"); | |
return result; | |
}) | |
}); | |
((ICallback) layouterMock.Setup(setupBeginPipelineExpression)) | |
.Callback<object, LayoutingContext, CancellationToken>((item, context, token) => | |
{ | |
Task.Run(() => | |
{ | |
var layouter = (ILayouter) layouterMock.Object; | |
foreach (var nextContext in context.InputPipe.Reader.GetConsumingEnumerable(token)) | |
{ | |
try | |
{ | |
LayoutResult result = layouter.GetNextLayout(item, nextContext); | |
context.OutputPipe.Writer.Write(result); | |
} | |
catch (Exception e) | |
{ | |
LayoutResult result = new LayoutResult(item as ContentItem); | |
result.ReportException(e); | |
context.OutputPipe.Writer.Write(result); | |
throw; | |
} | |
} | |
}); | |
}); | |
} | |
// Defer the current Mock<ILayouter<T>> to the list of mocks so it | |
// will be registered later in the DI together with its object ILayouter<T> | |
listOfMocks.Add(layouterMock); | |
var rendererType = typeof(IRenderer<>).MakeGenericType(contentItemType); | |
// Create the Mock<IRenderer<T>> instance | |
// f.e. Mock<IRenderer<Line>> | |
var rendererMockType = typeof(Mock<>).MakeGenericType(rendererType); | |
dynamic rendererMock = (Mock) Activator.CreateInstance(rendererMockType); | |
{ | |
// dynamically create the following lambda | |
// Expression<Action<IRenderer<T>>> setupExpression = renderer => | |
// renderer.Render(It.IsAny<LayoutSnapshot>()); | |
// 1. renderer | |
var rendererParameter = Expression.Parameter(rendererType, "renderer"); | |
// 2. renderer.Render(It.IsAny<LayoutSnapshot>()) | |
var callToRender = Expression.Call(rendererParameter, | |
typeof(IRenderer).GetMethod("Render", BindingFlags.Instance | BindingFlags.Public), | |
Expression.Call(typeof(It), "IsAny", new[] {typeof(LayoutSnapshot)})); | |
// 3. renderer => renderer.Render(It.IsAny<LayoutSnapshot>()) | |
dynamic setupExpression = Expression.Lambda(typeof(Action<>).MakeGenericType(rendererType), | |
callToRender, rendererParameter); | |
// dynamically add the tracing for calls of Render() method | |
((ICallback) rendererMock | |
.Setup(setupExpression)) | |
.Callback<LayoutSnapshot>(snapshot => | |
{ | |
serviceProviderMock.Object | |
.GetRequiredService<IEventTracingController>() | |
.AppendSnapshot(snapshot, nativeDocMock.Object); | |
}); | |
} | |
// Defer the current Mock<IRenderer<T>> to the list of mocks so it | |
// will be registered later in the DI together with its object IRenderer<T> | |
listOfMocks.Add(rendererMock); | |
} | |
// Setup the renderer factory which gets the renderers from the service provider | |
rendererFactoryMock | |
.Setup(rendererFactory => rendererFactory.GetRenderer(It.IsAny<ContentItem>())) | |
.Returns<ContentItem>(contentItem => | |
(IRenderer) serviceProviderMock.Object.GetService( | |
typeof(IRenderer<>).MakeGenericType(contentItem.GetType()))); | |
// Setup the layouter factory which gets the layouters from the service provider | |
layoutProviderMock | |
.Setup(layoutProvider => layoutProvider.GetLayouter(It.IsAny<Type>())) | |
.Returns<Type>(itemType => | |
(ILayouter) serviceProviderMock.Object.GetService( | |
typeof(ILayouter<>).MakeGenericType(itemType))); | |
// Setup document service that will be used for adding the pages | |
docServiceMock | |
.Setup(service => service.NativeDocument) | |
.Returns(() => nativeDocMock.Object); | |
docServiceMock | |
.Setup(service => service.AddNewPage(It.IsAny<float>(), It.IsAny<float>())) | |
.Callback(() => | |
{ | |
serviceProviderMock.Object.GetRequiredService<IEventTracingController>() | |
.AppendMessage($"Page {++nativeDocMock.Object.CurrentPage} created"); | |
}) | |
.Returns(() => nativeDocMock.Object.CurrentPage); | |
// Setup event bus events | |
/*eventBusMock | |
.Setup(bus => bus.Publish(It.IsAny<NewPageCreated>())) | |
.Callback<NewPageCreated>(newPageCreated => | |
OnControllerEventOccurred($"Page {newPageCreated.PageNumber} created"));*/ | |
// Setup controller context and section controller services | |
serviceProviderMock | |
.Setup(serviceProvider => serviceProvider.GetService(typeof(ControllerContext))) | |
.Returns(() => | |
ActivatorUtilities.CreateInstance(serviceProviderMock.Object, typeof(ControllerContext))); | |
serviceProviderMock | |
.Setup(serviceProvider => serviceProvider.GetService(typeof(SectionController))) | |
.Returns(() => | |
ActivatorUtilities.CreateInstance(serviceProviderMock.Object, typeof(SectionController))); | |
// For each mock created above... | |
foreach (var mockInstance in listOfMocks) | |
{ | |
// Add the instance of current mock to the service provider so we can call | |
// f.e. serviceProvider.GetService<Mock<IEventBus>>() that returns Mock<IEventBus> | |
// and then run additional setup actions or verification for this mock instance | |
// outside of current property | |
serviceProviderMock | |
.Setup(serviceProvider => serviceProvider.GetService(mockInstance.GetType())) | |
.Returns(mockInstance); | |
// Get the type of generic parameter of current mock | |
// f.e. for Mock<IEventBus> we need to get the type of IEventBus | |
var mockGenericParameter = mockInstance.GetType() | |
.GetGenericArguments()[0]; | |
// Register the service returning its mocked object | |
// f.e. GetService(typeof(IEventBus)) returns Mock<IEventBus>.Object | |
// Instead of exact value the mock returns the lambda because we don't want to | |
// initialize the mock object immediately just here | |
// (mock object will be initialized later if called by GetService) | |
serviceProviderMock | |
.Setup(serviceProvider => serviceProvider.GetService(mockGenericParameter)) | |
.Returns(() => mockInstance.Object) | |
/*.Callback<Type>(t => | |
{ | |
if (typeof(IRenderer).IsAssignableFrom(t)) | |
serviceProviderMock.Object.GetRequiredService<IEventTracingController>() | |
.AppendMessage(t.ToPrettyName() + " created"); | |
})*/; | |
} | |
// Add the tracing for internal events | |
serviceProviderMock | |
.Setup(serviceProvider => serviceProvider.GetService(typeof(IEventTracingController))) | |
.Returns(() => new EventTracingController(occurredEvents, snapshots)); | |
return serviceProviderMock.Object; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment