Skip to content

Commit 64be1b3

Browse files
committed
Extracted ActionCallExpressionInvoker (#282)
1 parent 4026bc8 commit 64be1b3

File tree

8 files changed

+138
-89
lines changed

8 files changed

+138
-89
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
<img src="https://raw.githubusercontent.com/ivaylokenov/MyTested.AspNetCore.Mvc/version-2.2/tools/test-sample.gif" />
2828

29-
**MyTested.AspNetCore.Mvc** has [more than 500 assertion methods](https://MyTestedASP.NET/Core/Mvc/Features) and is 100% covered by [more than 2000 unit tests](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc/tree/version-2.2/test). It should work correctly. Almost all items in the [issues page](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc/issues) are expected future features and enhancements.
29+
**MyTested.AspNetCore.Mvc** has [more than 500 assertion methods](https://MyTestedASP.NET/Core/Mvc/Features) and is 100% covered by [more than 2500 unit tests](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc/tree/version-2.2/test). It should work correctly. Almost all items in the [issues page](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc/issues) are expected future features and enhancements.
3030

3131
**MyTested.AspNetCore.Mvc** helps you speed up the testing process in your web development team! If you find that statement unbelievable, these are the words which some of the many happy **MyTested.AspNetCore.Mvc** users once said:
3232
> "I’ve been using your packages for almost 3 years now and it has saved me countless hours in creating unit tests and wanted to thank you for making this. I cannot imagine how much code I would have had to write to create the 450+ and counting unit tests I have for my controllers."

samples/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ Here you can find a few working samples, which will get you started with the lib
1414
- **ApplicationParts** - minimalistic functional sample testing whether controllers are found correctly when registered from external assemblies. Uses manual `Startup` configuration and [NUnit](https://github.com/nunit/dotnet-test-nunit).
1515
- **NoStartup** - minimalistic functional sample showing how to use the testing library without any globally configured `Startup` class. Uses [MSTest](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest).
1616
- **Lite** - minimalistic functional sample showing how to use the completely **FREE** and **UNLIMITED** version of the library - `MyTested.AspNetCore.Mvc.Lite`. Tests API controllers by using automatically resolved `TestStartup` class and [Moq](https://github.com/moq/moq4).
17-
- **FullFramework** - minimalistic functional samples showing how to use **MyTested.AspNetCore.Mvc** with the full .NET Framework. Uses [xUnit](http://xunit.github.io/).
1817
- **WebStartup** - minimalistic functional sample showing how to use the testing library with the web application's `Startup` class. Uses [xUnit](http://xunit.github.io/).

src/MyTested.AspNetCore.Mvc.Abstractions/Builders/Components/BaseComponentInvocationBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010

1111
public partial class BaseComponentBuilder<TComponent, TTestContext, TBuilder>
1212
{
13-
protected void Invoke<TMethodResult>(Expression<Func<TComponent, TMethodResult>> methodCall)
13+
protected void InvokeResult<TMethodResult>(Expression<Func<TComponent, TMethodResult>> methodCall)
1414
{
1515
var actionInfo = this.GetAndValidateMethodResult(methodCall);
1616
this.TestContext.Apply(actionInfo);
1717
}
1818

19-
protected void Invoke<TMethodResult>(Expression<Func<TComponent, Task<TMethodResult>>> methodCall)
19+
protected void InvokeAsyncResult<TMethodResult>(Expression<Func<TComponent, Task<TMethodResult>>> methodCall)
2020
{
2121
var methodInfo = this.GetAndValidateMethodResult(methodCall);
2222
var methodResult = default(TMethodResult);
@@ -34,7 +34,7 @@ protected void Invoke<TMethodResult>(Expression<Func<TComponent, Task<TMethodRes
3434
this.TestContext.MethodResult = methodResult;
3535
}
3636

37-
protected void Invoke(Expression<Action<TComponent>> methodCall)
37+
protected void InvokeVoid(Expression<Action<TComponent>> methodCall)
3838
{
3939
var methodName = this.GetAndValidateMethod(methodCall);
4040
Exception caughtException = null;
@@ -54,7 +54,7 @@ protected void Invoke(Expression<Action<TComponent>> methodCall)
5454
this.TestContext.MethodResult = VoidMethodResult.Instance;
5555
}
5656

57-
protected void Invoke(Expression<Func<TComponent, Task>> methodCall)
57+
protected void InvokeAsyncVoid(Expression<Func<TComponent, Task>> methodCall)
5858
{
5959
var methodInfo = this.GetAndValidateMethodResult(methodCall);
6060

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public static bool IsNotGeneric(Type type)
105105
public static bool IsGenericTypeDefinition(Type type)
106106
=> type.GetTypeInfo().IsGenericTypeDefinition;
107107

108+
public static bool HasGenericTypeDefinition(Type type, Type genericTypeDefinition)
109+
=> type.IsGenericType && type.GetGenericTypeDefinition() == genericTypeDefinition;
110+
108111
/// <summary>
109112
/// Checks whether two types are assignable by generic definition.
110113
/// </summary>
@@ -363,6 +366,9 @@ public static bool IsAnonymousType(Type type)
363366
&& (typeInfo.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
364367
}
365368

369+
public static MethodInfo GetNonPublicMethod(Type type, string name)
370+
=> type.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
371+
366372
private static ConstructorInfo GetConstructorByUnorderedParameters(this Type type, IEnumerable<Type> types)
367373
{
368374
if (TypesWithOneConstructorCache.TryGetValue(type, out var cachedConstructor))

src/MyTested.AspNetCore.Mvc.Controllers/Builders/Controllers/ControllerActionCallBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,28 @@ public partial class ControllerBuilder<TController>
1414
/// <inheritdoc />
1515
public IActionResultTestBuilder<TActionResult> Calling<TActionResult>(Expression<Func<TController, TActionResult>> actionCall)
1616
{
17-
this.Invoke(actionCall);
17+
this.InvokeResult(actionCall);
1818
return new ActionResultTestBuilder<TActionResult>(this.TestContext);
1919
}
2020

2121
/// <inheritdoc />
2222
public IActionResultTestBuilder<TActionResult> Calling<TActionResult>(Expression<Func<TController, Task<TActionResult>>> actionCall)
2323
{
24-
this.Invoke(actionCall);
24+
this.InvokeAsyncResult(actionCall);
2525
return new ActionResultTestBuilder<TActionResult>(this.TestContext);
2626
}
2727

2828
/// <inheritdoc />
2929
public IVoidActionResultTestBuilder Calling(Expression<Action<TController>> actionCall)
3030
{
31-
this.Invoke(actionCall);
31+
this.InvokeVoid(actionCall);
3232
return new VoidActionResultTestBuilder(this.TestContext);
3333
}
3434

3535
/// <inheritdoc />
3636
public IVoidActionResultTestBuilder Calling(Expression<Func<TController, Task>> actionCall)
3737
{
38-
this.Invoke(actionCall);
38+
this.InvokeAsyncVoid(actionCall);
3939
return new VoidActionResultTestBuilder(this.TestContext);
4040
}
4141
}
Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
namespace MyTested.AspNetCore.Mvc.Builders.Pipeline
22
{
3-
using System;
4-
using System.Linq;
5-
using System.Linq.Expressions;
6-
using System.Reflection;
7-
using System.Threading.Tasks;
83
using Builders.Actions;
94
using Builders.Contracts.Actions;
105
using Builders.Contracts.Base;
@@ -15,11 +10,19 @@
1510
using Internal.TestContexts;
1611
using Utilities;
1712

13+
/// <summary>
14+
/// Used for building the controller which will be tested after a route assertion.
15+
/// </summary>
16+
/// <typeparam name="TController">Class representing ASP.NET Core MVC controller.</typeparam>
1817
public class WhichControllerInstanceBuilder<TController>
1918
: BaseControllerBuilder<TController, IAndWhichControllerInstanceBuilder<TController>>,
2019
IAndWhichControllerInstanceBuilder<TController>
2120
where TController : class
2221
{
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="WhichControllerInstanceBuilder{TController}"/> class.
24+
/// </summary>
25+
/// <param name="testContext"><see cref="ControllerTestContext"/> containing data about the currently executed assertion chain.</param>
2326
public WhichControllerInstanceBuilder(ControllerTestContext testContext)
2427
: base(testContext)
2528
{
@@ -38,97 +41,35 @@ public IShouldThrowTestBuilder ShouldThrow()
3841
.ShouldThrow();
3942

4043
/// <inheritdoc />
41-
public IShouldReturnTestBuilder<MethodResult> ShouldReturn()
42-
{
43-
this.InvokeAction();
44-
// epa iznesi tuka IActionResultInterface che da e po-lesno iznasqneto tuk + iznesi dolu Invoke na nshto kato ActionCallExpression.Invoke()
45-
var actionResultTestBuilder = new ActionResultTestBuilder<MethodResult>(this.TestContext);
46-
47-
return actionResultTestBuilder.ShouldReturn();
48-
}
44+
public IBaseTestBuilderWithInvokedAction ShouldReturnEmpty()
45+
=> this
46+
.InvokeAndGetActionResultTestBuilder()
47+
.ShouldReturnEmpty();
4948

5049
/// <inheritdoc />
51-
public IBaseTestBuilderWithInvokedAction ShouldReturnEmpty()
50+
public IShouldReturnTestBuilder<MethodResult> ShouldReturn()
5251
{
5352
this.InvokeAction();
5453

55-
var actionResultTestBuilder = new VoidActionResultTestBuilder(this.TestContext);
54+
var actionResultTestBuilder = new ActionResultTestBuilder<MethodResult>(this.TestContext);
5655

57-
return actionResultTestBuilder.ShouldReturnEmpty();
56+
return actionResultTestBuilder.ShouldReturn();
5857
}
5958

6059
/// <inheritdoc />
6160
public IWhichControllerInstanceBuilder<TController> AndAlso() => this;
6261

6362
protected override IAndWhichControllerInstanceBuilder<TController> SetBuilder() => this;
6463

65-
private IBaseActionResultTestBuilder<MethodResult> InvokeAndGetActionResultTestBuilder()
64+
private VoidActionResultTestBuilder InvokeAndGetActionResultTestBuilder()
6665
{
6766
this.InvokeAction();
6867

6968
return new VoidActionResultTestBuilder(this.TestContext);
7069
}
7170

7271
private void InvokeAction()
73-
{
74-
var methodCall = this.TestContext.MethodCall;
75-
var methodInfo = ExpressionParser.GetMethodInfo(methodCall);
76-
77-
if (methodInfo.ReturnType == typeof(void))
78-
{
79-
var actionCall = Expression.Lambda<Action<TController>>(
80-
methodCall.Body,
81-
methodCall.Parameters);
82-
83-
this.Invoke(actionCall);
84-
}
85-
else if (methodInfo.ReturnType == typeof(Task))
86-
{
87-
var actionCall = Expression.Lambda<Func<TController, Task>>(
88-
methodCall.Body,
89-
methodCall.Parameters);
90-
91-
this.Invoke(actionCall);
92-
}
93-
else
94-
{
95-
var methodName = nameof(this.CallInvoke);
96-
var returnType = methodInfo.ReturnType;
97-
98-
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
99-
{
100-
methodName = nameof(this.CallTaskInvoke);
101-
returnType = returnType.GetGenericArguments().First();
102-
}
103-
104-
var invokeActionOpenGeneric = this.GetType().GetMethod(
105-
methodName,
106-
BindingFlags.Instance | BindingFlags.NonPublic);
107-
108-
var invokeActionClosedGeneric = invokeActionOpenGeneric.MakeGenericMethod(returnType);
109-
110-
invokeActionClosedGeneric.Invoke(this, new[] { methodCall });
111-
}
112-
}
113-
114-
// Called via reflection.
115-
private void CallInvoke<TActionResult>(LambdaExpression methodCall)
116-
{
117-
var actionCall = Expression.Lambda<Func<TController, TActionResult>>(
118-
methodCall.Body,
119-
methodCall.Parameters);
120-
121-
this.Invoke(actionCall);
122-
}
123-
124-
// Called via reflection.
125-
private void CallTaskInvoke<TActionResult>(LambdaExpression methodCall)
126-
{
127-
var actionCall = Expression.Lambda<Func<TController, Task<TActionResult>>>(
128-
methodCall.Body,
129-
methodCall.Parameters);
130-
131-
this.Invoke(actionCall);
132-
}
72+
=> ActionCallExpressionInvoker<TController>
73+
.Invoke(this.TestContext.MethodCall, this);
13374
}
13475
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
namespace MyTested.AspNetCore.Mvc.Utilities
2+
{
3+
using System;
4+
using System.Linq;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
using System.Threading.Tasks;
8+
using Builders.Pipeline;
9+
10+
public static class ActionCallExpressionInvoker<TController>
11+
where TController : class
12+
{
13+
private static readonly Type ActionCallExpressionInvokerType = typeof(ActionCallExpressionInvoker<TController>);
14+
15+
private static readonly Type VoidType = typeof(void);
16+
private static readonly Type TaskType = typeof(Task);
17+
private static readonly Type GenericTaskType = typeof(Task<>);
18+
19+
private static readonly Type BuilderType = typeof(WhichControllerInstanceBuilder<TController>);
20+
21+
private static readonly MethodInfo InvokeResultMethod = Reflection.GetNonPublicMethod(BuilderType, "InvokeResult");
22+
private static readonly MethodInfo InvokeAsyncResultMethod = Reflection.GetNonPublicMethod(BuilderType, "InvokeAsyncResult");
23+
private static readonly MethodInfo InvokeVoidMethod = Reflection.GetNonPublicMethod(BuilderType, "InvokeVoid");
24+
private static readonly MethodInfo InvokeAsyncVoidMethod = Reflection.GetNonPublicMethod(BuilderType, "InvokeAsyncVoid");
25+
26+
public static void Invoke(
27+
LambdaExpression methodCall,
28+
WhichControllerInstanceBuilder<TController> builder)
29+
{
30+
var methodInfo = ExpressionParser.GetMethodInfo(methodCall);
31+
var methodReturnType = methodInfo.ReturnType;
32+
33+
if (Reflection.AreSameTypes(methodReturnType, VoidType))
34+
{
35+
var actionCall = Expression.Lambda<Action<TController>>(
36+
methodCall.Body,
37+
methodCall.Parameters);
38+
39+
InvokeVoidMethod.Invoke(builder, new[] { actionCall });
40+
}
41+
else if (Reflection.AreSameTypes(methodReturnType, TaskType))
42+
{
43+
var actionCall = Expression.Lambda<Func<TController, Task>>(
44+
methodCall.Body,
45+
methodCall.Parameters);
46+
47+
InvokeAsyncVoidMethod.Invoke(builder, new[] { actionCall });
48+
}
49+
else
50+
{
51+
var methodName = nameof(CallInvokeResult);
52+
var invokeMethod = InvokeResultMethod;
53+
54+
if (Reflection.HasGenericTypeDefinition(methodReturnType, GenericTaskType))
55+
{
56+
methodName = nameof(CallAsyncResult);
57+
invokeMethod = InvokeAsyncResultMethod;
58+
methodReturnType = methodReturnType.GetGenericArguments().First();
59+
}
60+
61+
var invokeActionOpenGeneric = Reflection.GetNonPublicMethod(
62+
ActionCallExpressionInvokerType,
63+
methodName);
64+
65+
var invokeActionClosedGeneric = invokeActionOpenGeneric
66+
.MakeGenericMethod(methodReturnType);
67+
68+
invokeActionClosedGeneric
69+
.Invoke(null, new object[] { methodCall, builder, invokeMethod });
70+
}
71+
}
72+
73+
// Called via reflection.
74+
private static void CallInvokeResult<TActionResult>(
75+
LambdaExpression methodCall,
76+
WhichControllerInstanceBuilder<TController> builder,
77+
MethodInfo invokeResultMethod)
78+
{
79+
var actionCall = Expression.Lambda<Func<TController, TActionResult>>(
80+
methodCall.Body,
81+
methodCall.Parameters);
82+
83+
invokeResultMethod
84+
.MakeGenericMethod(typeof(TActionResult))
85+
.Invoke(builder, new[] { actionCall });
86+
}
87+
88+
// Called via reflection.
89+
private static void CallAsyncResult<TActionResult>(
90+
LambdaExpression methodCall,
91+
WhichControllerInstanceBuilder<TController> builder,
92+
MethodInfo invokeAsyncResultMethod)
93+
{
94+
var actionCall = Expression.Lambda<Func<TController, Task<TActionResult>>>(
95+
methodCall.Body,
96+
methodCall.Parameters);
97+
98+
invokeAsyncResultMethod
99+
.MakeGenericMethod(typeof(TActionResult))
100+
.Invoke(builder, new[] { actionCall });
101+
}
102+
}
103+
}

src/MyTested.AspNetCore.Mvc.ViewComponents/Builders/ViewComponents/ViewComponentInvocationBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ public partial class ViewComponentBuilder<TViewComponent>
1616
/// <inheritdoc />
1717
public IViewComponentResultTestBuilder<TInvocationResult> InvokedWith<TInvocationResult>(Expression<Func<TViewComponent, TInvocationResult>> invocationCall)
1818
{
19-
this.Invoke(invocationCall);
19+
this.InvokeResult(invocationCall);
2020
return new ViewComponentResultTestBuilder<TInvocationResult>(this.TestContext);
2121
}
2222

2323
/// <inheritdoc />
2424
public IViewComponentResultTestBuilder<TInvocationResult> InvokedWith<TInvocationResult>(Expression<Func<TViewComponent, Task<TInvocationResult>>> invocationCall)
2525
{
26-
this.Invoke(invocationCall);
26+
this.InvokeAsyncResult(invocationCall);
2727
return new ViewComponentResultTestBuilder<TInvocationResult>(this.TestContext);
2828
}
2929

0 commit comments

Comments
 (0)