Since your type is a generic parameter, AssemblyQualifiedName
will be null. Instead, parameterType.GetRealTypeForByRefType()
should almost certainly just be replaced by parameterType.GetElementType()
.
Mocking Framework Can't Handle Generic Reference-Type Parameters
-
04-07-2023 - |
Question
We have been using Simple.Mocking as our mocking framework for unit testing. Recently, I made a change to our ICacheService, which is heavily used in our unit testing and mocking.
The change I made was add a method similar to the following:
bool TrySetCache(string key,T data,ref T value);
The semantics of this implementation is irrelevant so please do not give feedback on the use of reference-type parameters. :)
Once our CI build kicked off and ran our automated tests, they all failed due to mock type creation failing.
I looked deeper into the project and realized that it was failing when it tried to mock the generic reference-type parameter.
The following coee handles emitting IL for the mocked method parameters:
var baseMethodParameters = baseMethod.GetParameters();
ilGenerator.Emit(OpCodes.Ldc_I4, baseMethodParameters.Length);
ilGenerator.Emit(OpCodes.Newarr, typeof(object));
ilGenerator.Emit(OpCodes.Stloc_2);
for (int i = 0; i < baseMethodParameters.Length; i++)
{
var parameter = baseMethodParameters[i];
var parameterType = parameter.ParameterType;
ilGenerator.Emit(OpCodes.Ldloc_2);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldarg, i + 1);
if (parameterType.IsByRef)
{
parameterType = parameterType.GetRealTypeForByRefType();
ilGenerator.EmitLoadIndirect(parameterType);
}
EmitBoxValue(ilGenerator, parameterType);
ilGenerator.Emit(OpCodes.Stelem_Ref);
}
More specifically, the code for handling the reference-type parameter:
if (parameterType.IsByRef)
{
parameterType = parameterType.GetRealTypeForByRefType();
ilGenerator.EmitLoadIndirect(parameterType);
}
This calls the extension method that is failing when a generic reference-type parameter is used:
const string ByRefSpecifier = "&";
public static Type GetRealTypeForByRefType(this Type type)
{
if (!type.IsByRef)
throw new InvalidOperationException();
return Type.GetType(type.AssemblyQualifiedName.Replace(ByRefSpecifier, string.Empty), true);
}
The return statement fails.
I would like to fix this, but I am not sure if its actually possible to infer the usage and mock a generic reference-type parameter.
If it is in fact not possible, then I will just end up adding a check to see if it is a generic-type parameter, and if it is, give an error message so users dont need to troubleshoot for too long to figure out the issue.
IMPORTANT NOTE One thing I did notice when I was debugging this was that when it reflects on my ICacheService.TrySetCache(string key,T data,ref T value), it does not mark the last parameter as a generic parameter type.
Solution