How do I unit test a C# function which returns a Func<something>?
I have a class containing a method which returns a Result object which contains a property of type Func.
class Result {
public Func<Result> NextAction { get; set; }
}
How do I wr开发者_如何学JAVAite a unit test assertion regarding the content of this Func? The following obviously does not work, because the compiler generates two different methods for the lambda:
// Arrange
ListController controller = new ListController(domain);
// Act
Result actual = controller.DefaultAction();
// Assert
Func<Result> expected = () => new ProductsController(domain).ListAction();
Assert.That(actual.NextAction, Is.EqualTo(expected));
I'm guessing that I could do this by using expression trees instead, but... is there a way to avoid doing so? I'm using NUnit 2.5.
EDIT: There are no other identifying fields in the Result object. It is intended to be a way of invoking the next object/method based on a decision made in the current object/method.
Why not invoke the Func
and compare the returned values?
var actualValue = actual.NextAction();
var expectedValue = expected();
Assert.That(actualValue, Is.EqualTo(expectedValue));
EDIT: I see that the Result class does not have any identity. I guess you have some other fields in the Result class that define the identity of the Result and can be used to determine if two results are equal.
I'm not aware of an easy way to look inside a lambda (other than using expression trees as you said) but it is possible to compare delegates if they're assigned a method group instead.
var result1 = new Result {
NextAction = new ProductsController(domain).ListAction };
var result2 = new Result {
NextAction = new ProductsController(domain).ListAction };
//objects are different
Assert.That(result1, Is.Not.EqualTo(result2));
//delegates are different
Assert.That(result1.NextAction, Is.Not.EqualTo(result2.NextAction));
//methods are the same
Assert.That(result1.NextAction.Method, Is.EqualTo(result2.NextAction.Method));
The above example does not work if you use lambdas since they are compiled to different methods.
If you Func<Result>
always return the same result you can test which object is returned by the function.
Well it appears that unit testing the contents of a Func
goes beyond the normal range of unit testing. A Func
represents compiled code, and therefore can not be further inspected without resorting to parsing MSIL. In this situation, it is therefore necessary to fall back on delegates and instantiated types (as suggested by Nathan Baulch), or to use expression trees instead.
My expression tree equivalent below:
class Result {
public Expression<Func<Result>> NextAction { get; set; }
}
with the unit testing as follows:
// Arrange
ListController controller = new ListController(domain);
// Act
Result actual = controller.DefaultAction();
// Assert
MethodCallExpression methodExpr = (MethodCallExpression)actual.NextAction.Body;
NewExpression newExpr = (NewExpression)methodExpr.Object;
Assert.That(newExpr.Type, Is.EqualTo(typeof(ProductsController)));
Assert.That(methodExpr.Method.Name, Is.EqualTo("ListAction"));
Note that there is some inherent fragility to this test as it implies the structure of the expression, as well as its behaviour.
If I understand the issue properly the NextAction may or may not have a different lambda implementation, which is what needs testing.
In the example below I compare the methods IL bytes. Using reflection, get the method info and IL bytes from the body in an array. If the byte arrays match, the lambda's are the same.
There are lots of situations that this wont handle, but if it's just question of comparing two lambda's that should be exactly the same, this will work. Sorry it's in MSTest :)
using System.Reflection;
....
[TestClass]
public class Testing
{
[TestMethod]
public void Results_lambdas_match( )
{
// Arrange
ListController testClass = new ListController( );
Func<Result> expected = ( ) => new ProductsController( ).ListAction( );
Result actual;
byte[ ] actualMethodBytes;
byte[ ] expectedMethodBytes;
// Act
actual = testClass.DefaultAction( );
// Assert
actualMethodBytes = actual.NextAction.
Method.GetMethodBody( ).GetILAsByteArray( );
expectedMethodBytes = expected.
Method.GetMethodBody( ).GetILAsByteArray( );
// Test that the arrays are the same, more rigorous check really should
// be done .. but this is an example :)
for ( int count=0; count < actualMethodBytes.Length; count++ )
{
if ( actualMethodBytes[ count ] != expectedMethodBytes[ count ] )
throw new AssertFailedException(
"Method implementations are not the same" );
}
}
[TestMethod]
public void Results_lambdas_do_not_match( )
{
// Arrange
ListController testClass = new ListController( );
Func<Result> expected = ( ) => new OtherController( ).ListAction( );
Result actual;
byte[ ] actualMethodBytes;
byte[ ] expectedMethodBytes;
int count=0;
// Act
actual = testClass.DefaultAction( );
// Assert
actualMethodBytes = actual.NextAction.
Method.GetMethodBody( ).GetILAsByteArray( );
expectedMethodBytes = expected.
Method.GetMethodBody( ).GetILAsByteArray( );
// Test that the arrays aren't the same, more checking really should
// be done .. but this is an example :)
for ( ; count < actualMethodBytes.Length; count++ )
{
if ( actualMethodBytes[ count ] != expectedMethodBytes[ count ] )
break;
}
if ( ( count + 1 == actualMethodBytes.Length )
&& ( actualMethodBytes.Length == expectedMethodBytes.Length ) )
throw new AssertFailedException(
"Method implementations are the same, they should NOT be." );
}
public class Result
{
public Func<Result> NextAction { get; set; }
}
public class ListController
{
public Result DefaultAction( )
{
Result result = new Result( );
result.NextAction = ( ) => new ProductsController( ).ListAction( );
return result;
}
}
public class ProductsController
{
public Result ListAction( ) { return null; }
}
public class OtherController
{
public Result ListAction( ) { return null; }
}
}
精彩评论