Skip to content

Commit c1afc25

Browse files
committed
Ported DataAnnotations.Test package (#326)
1 parent 299284b commit c1afc25

File tree

53 files changed

+296
-66
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+296
-66
lines changed

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/Http/HttpResponseMock.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
{
33
using System;
44
using System.IO;
5+
using System.IO.Pipelines;
6+
using System.Threading;
57
using System.Threading.Tasks;
68
using Microsoft.AspNetCore.Http;
79

@@ -26,7 +28,7 @@ public override Stream Body
2628
get => this.httpResponse.Body;
2729
set => this.httpResponse.Body = value;
2830
}
29-
31+
3032
public override long? ContentLength
3133
{
3234
get => this.httpResponse.ContentLength;
@@ -39,6 +41,8 @@ public override string ContentType
3941
set => this.httpResponse.ContentType = value;
4042
}
4143

44+
public override PipeWriter BodyWriter => this.httpResponse.BodyWriter;
45+
4246
public override IResponseCookies Cookies => this.httpResponse.Cookies;
4347

4448
public override bool HasStarted => this.httpResponse.HasStarted;
@@ -74,6 +78,14 @@ public override void Redirect(string location, bool permanent)
7478
=> this.httpResponse
7579
.Redirect(location, permanent);
7680

81+
public override Task StartAsync(CancellationToken cancellationToken = default)
82+
=> this.httpResponse
83+
.StartAsync(cancellationToken);
84+
85+
public override Task CompleteAsync()
86+
=> this.httpResponse
87+
.CompleteAsync();
88+
7789
/// <summary>
7890
/// Does nothing. Intentionally left empty, otherwise some HTTP features are not working correctly.
7991
/// </summary>

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/Routing/ModelBindingActionInvoker.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
using Microsoft.AspNetCore.Mvc.Filters;
1010
using Microsoft.AspNetCore.Mvc.Infrastructure;
1111
using Microsoft.Extensions.Logging;
12+
using Utilities.Extensions;
1213
using Utilities.Validators;
1314

1415
public class ModelBindingActionInvoker : IModelBindingActionInvoker
1516
{
16-
private readonly dynamic cacheEntry;
1717
private readonly ControllerContext controllerContext;
18+
private readonly dynamic cacheEntry;
19+
private readonly dynamic cacheEntryMock;
1820
private readonly object invoker;
1921

2022
private Dictionary<string, object> arguments;
@@ -26,16 +28,21 @@ public ModelBindingActionInvoker(
2628
IActionResultTypeMapper mapper,
2729
ControllerContext controllerContext,
2830
dynamic cacheEntry,
31+
dynamic cacheEntryMock,
2932
IFilterMetadata[] filters)
3033
{
3134
CommonValidator.CheckForNullReference(cacheEntry, nameof(cacheEntry));
32-
33-
this.cacheEntry = cacheEntry;
35+
CommonValidator.CheckForNullReference(cacheEntryMock, nameof(cacheEntryMock));
36+
CommonValidator.CheckForNullReference(controllerContext, nameof(controllerContext));
37+
3438
this.controllerContext = controllerContext;
3539

40+
this.cacheEntry = cacheEntry;
41+
this.cacheEntryMock = cacheEntryMock;
42+
3643
var invokerType = WebFramework.Internals.ControllerActionInvoker;
3744
var constructor = invokerType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
38-
this.invoker = constructor.Invoke(new object[] { logger, diagnosticListener, actionContextAccessor, mapper, controllerContext, cacheEntry, filters });
45+
this.invoker = constructor.Invoke(new object[] { logger, diagnosticListener, actionContextAccessor, mapper, controllerContext, cacheEntryMock, filters });
3946
}
4047

4148
public IDictionary<string, object> BoundActionArguments => this.arguments;
@@ -53,11 +60,13 @@ public async Task InvokeAsync()
5360
return;
5461
}
5562

56-
await (Task)this.invoker.GetType().GetMethod(nameof(this.InvokeAsync)).Invoke(this.invoker, null);
63+
// Invokes the filter pipeline and executes a mocked action.
64+
await this.invoker.Exposed().InvokeAsync();
5765

58-
var controllerInstance = this.cacheEntry.ControllerFactory(this.controllerContext);
66+
// Invokes the model binding on the real action.
67+
var controllerInstance = this.cacheEntry.ControllerFactory.Invoke(this.controllerContext);
5968

60-
await this.cacheEntry.ControllerBinderDelegate(this.controllerContext, controllerInstance, this.arguments);
69+
await this.cacheEntry.ControllerBinderDelegate.DynamicInvoke(this.controllerContext, controllerInstance, this.arguments);
6170
}
6271
}
6372
}
Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
namespace MyTested.AspNetCore.Mvc.Internal.Routing
22
{
3-
using System;
43
using Microsoft.AspNetCore.Mvc;
54
using Services;
5+
using Utilities.Extensions;
66

77
public class ModelBindingActionInvokerCache
88
{
9-
private dynamic methodDelegate;
9+
private dynamic instance;
1010

1111
public dynamic GetCachedResult(ControllerContext controllerContext)
1212
{
13-
if (this.methodDelegate == null)
13+
if (this.instance == null)
1414
{
1515
var type = WebFramework.Internals.ControllerActionInvokerCache;
16-
var instance = TestServiceProvider.GetService(type);
17-
var method = type.GetMethod(nameof(GetCachedResult));
18-
var typeOfDelegate = typeof(Func<,>).MakeGenericType(typeof(ControllerContext), method.ReturnType);
19-
this.methodDelegate = method.CreateDelegate(typeOfDelegate, instance);
16+
this.instance = TestServiceProvider.GetService(type).Exposed();
2017
}
2118

22-
return this.methodDelegate(controllerContext);
19+
return this.instance.GetCachedResult(controllerContext);
2320
}
2421
}
2522
}

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/Routing/ModelBindingActionInvokerFactory.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
namespace MyTested.AspNetCore.Mvc.Internal.Routing
22
{
3+
using System;
34
using System.Collections.Generic;
45
using System.Diagnostics;
56
using System.Linq;
7+
using System.Reflection;
8+
using System.Threading.Tasks;
69
using Actions;
710
using Contracts;
811
using Microsoft.AspNetCore.Mvc;
@@ -11,9 +14,14 @@
1114
using Microsoft.AspNetCore.Mvc.ModelBinding;
1215
using Microsoft.Extensions.Logging;
1316
using Microsoft.Extensions.Options;
17+
using Utilities;
18+
using Utilities.Extensions;
1419

1520
public class ModelBindingActionInvokerFactory : IModelBindingActionInvokerFactory
1621
{
22+
private static readonly TypeInfo RouteController = typeof(RouteControllerMock).GetTypeInfo();
23+
private static readonly MethodInfo RouteAction = RouteController.GetDeclaredMethod(nameof(RouteControllerMock.ActionMock));
24+
1725
private readonly ModelBindingActionInvokerCache modelBindingActionInvokerCache;
1826
private readonly IReadOnlyList<IValueProviderFactory> valueProviderFactories;
1927
private readonly int maxModelValidationErrors;
@@ -22,6 +30,16 @@ public class ModelBindingActionInvokerFactory : IModelBindingActionInvokerFactor
2230
private readonly IActionResultTypeMapper mapper;
2331
private readonly IActionContextAccessor actionContextAccessor;
2432

33+
public ModelBindingActionInvokerFactory(
34+
ModelBindingActionInvokerCache modelBindingActionInvokerCache,
35+
IOptions<MvcOptions> optionsAccessor,
36+
ILoggerFactory loggerFactory,
37+
DiagnosticListener diagnosticListener,
38+
IActionResultTypeMapper mapper)
39+
: this (modelBindingActionInvokerCache, optionsAccessor, loggerFactory, diagnosticListener, mapper, null)
40+
{
41+
}
42+
2543
public ModelBindingActionInvokerFactory(
2644
ModelBindingActionInvokerCache modelBindingActionInvokerCache,
2745
IOptions<MvcOptions> optionsAccessor,
@@ -50,14 +68,56 @@ public IActionInvoker CreateModelBindingActionInvoker(ActionContext actionContex
5068

5169
var cacheResult = this.modelBindingActionInvokerCache.GetCachedResult(controllerContext);
5270

71+
var cacheEntry = cacheResult.Item1; // cacheEntry
72+
var filters = cacheResult.Item2; // filters
73+
74+
dynamic exposedCacheEntry = new ExposedObject(cacheEntry);
75+
76+
Func<ControllerContext, object> controllerFactory = context => new RouteControllerMock();
77+
Action<ControllerContext, object> controllerReleaser = (context, instance) => { };
78+
Func<ControllerContext, object, Dictionary<string, object>, Task> controllerBinderDelegateFunc
79+
= (context, instance, arguments) => Task.CompletedTask;
80+
81+
var objectMethodExecutor = WebFramework.Internals.ObjectMethodExecutor
82+
.Exposed()
83+
.Create(RouteAction, RouteController);
84+
85+
var actionMethodExecutor = WebFramework.Internals.ActionMethodExecutor
86+
.Exposed()
87+
.GetExecutor(objectMethodExecutor);
88+
89+
var controllerBinderDelegateType = WebFramework.Internals.ControllerBinderDelegate;
90+
91+
var controllerBinderDelegate = typeof(DelegateExtensions)
92+
.GetMethod(nameof(DelegateExtensions.ConvertTo))
93+
.MakeGenericMethod(controllerBinderDelegateType)
94+
.Invoke(null, new object[] { controllerBinderDelegateFunc });
95+
96+
var cacheEntryObject = cacheEntry as object;
97+
98+
var cacheEntryMock = cacheEntryObject
99+
.GetType()
100+
.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
101+
.First()
102+
.Invoke(new object[]
103+
{
104+
exposedCacheEntry.CachedFilters,
105+
controllerFactory,
106+
controllerReleaser,
107+
controllerBinderDelegate,
108+
objectMethodExecutor.Object,
109+
actionMethodExecutor.Object
110+
});
111+
53112
return new ModelBindingActionInvoker(
54113
this.logger,
55114
this.diagnosticListener,
56115
this.actionContextAccessor,
57116
this.mapper,
58117
controllerContext,
59-
cacheResult.cacheEntry,
60-
cacheResult.filters);
118+
exposedCacheEntry,
119+
cacheEntryMock,
120+
filters);
61121
}
62122
}
63123
}

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/Routing/ModelBindingActionInvokerProvider.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ public class ModelBindingActionInvokerProvider : IActionInvokerProvider
99
{
1010
private readonly IModelBindingActionInvokerFactory actionInvokerFactory;
1111

12-
public ModelBindingActionInvokerProvider(IModelBindingActionInvokerFactory actionInvokerFactory)
13-
{
14-
this.actionInvokerFactory = actionInvokerFactory;
15-
}
12+
public ModelBindingActionInvokerProvider(IModelBindingActionInvokerFactory actionInvokerFactory)
13+
=> this.actionInvokerFactory = actionInvokerFactory;
1614

1715
public int Order => int.MaxValue;
1816

@@ -31,7 +29,7 @@ public void OnProvidersExecuting(ActionInvokerProviderContext context)
3129

3230
public void OnProvidersExecuted(ActionInvokerProviderContext context)
3331
{
34-
// intentionally does nothing
32+
// Intentionally does nothing.
3533
}
3634
}
3735
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace MyTested.AspNetCore.Mvc.Internal.Routing
2+
{
3+
internal class RouteControllerMock
4+
{
5+
public void ActionMock()
6+
{
7+
// Intentionally left empty. Used by the route testing services to fake the actual action call.
8+
}
9+
}
10+
}

src/MyTested.AspNetCore.Mvc.Abstractions/Internal/WebFramework.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ public static Type TypeActivatorCache
8989
}
9090
}
9191

92+
public static Type ObjectMethodExecutor
93+
{
94+
get
95+
{
96+
var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));
97+
var type = assembly.GetType("Microsoft.Extensions.Internal.ObjectMethodExecutor");
98+
return type;
99+
}
100+
}
101+
92102
public static Type AttributeRouting
93103
{
94104
get
@@ -139,6 +149,26 @@ public static Type ControllerActionInvokerCache
139149
}
140150
}
141151

152+
public static Type ControllerBinderDelegate
153+
{
154+
get
155+
{
156+
var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));
157+
var type = assembly.GetType("Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegate");
158+
return type;
159+
}
160+
}
161+
162+
public static Type ActionMethodExecutor
163+
{
164+
get
165+
{
166+
var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));
167+
var type = assembly.GetType("Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor");
168+
return type;
169+
}
170+
}
171+
142172
public static Type ExpressionHelper
143173
{
144174
get

src/MyTested.AspNetCore.Mvc.Abstractions/Utilities/AsyncHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66

77
public class AsyncHelper
88
{
9-
private static readonly TaskFactory taskFactory = new TaskFactory(
9+
private static readonly TaskFactory TaskFactory = new TaskFactory(
1010
CancellationToken.None,
1111
TaskCreationOptions.None,
1212
TaskContinuationOptions.None,
1313
TaskScheduler.Default);
1414

1515
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
16-
=> taskFactory
16+
=> TaskFactory
1717
.StartNew(func)
1818
.Unwrap()
1919
.ConfigureAwait(false)
2020
.GetAwaiter()
2121
.GetResult();
2222

2323
public static void RunSync(Func<Task> func)
24-
=> taskFactory
24+
=> TaskFactory
2525
.StartNew(func)
2626
.Unwrap()
2727
.ConfigureAwait(false)

0 commit comments

Comments
 (0)