views:

307

answers:

5

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 write 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.

+2  A: 

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.

Marek
Because the Func returns another Result object, with the same problems!
goofballLogic
Then you need to implement something that identifies the Result and determine equality based on that
Marek
Like an arbitrary string describing the Func's intent?
goofballLogic
There are no identifying fields in the Result class. It is intended to be a placeholder for dynamically invoking the next class/method based on a decision in the current class/method
goofballLogic
If there is nothing in the domain that defines an identity of the object, then an arbitrary string should do. But I suspect that you are trying to unit test something that is not intended to be unit tested - in the end, the program must have a value available and you should assert on the values, not on Funcs that provide them.
Marek
In an object whose responsibility is to decide the next action, I would have thought that unit testing that decision was of paramount importance.
goofballLogic
A: 

If you Func<Result> always return the same result you can test which object is returned by the function.

Andrew Bezzub
Unfortunately, the Func<Result> returns a Result which only contains another Func<Result>
goofballLogic
+2  A: 

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.

Nathan Baulch
Yes this is much like testing for event registration, or any other delegate use. While this may be the best compromise, the original intent was to include the instantiation of ProductsController as part of invoking NextAction.
goofballLogic
A: 

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.

goofballLogic
A: 

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; }
        }
    }
MarcLawrence
Yes, this could be a possibility. That could actually work, except that the code is passing in the "domain" parameter to the constructor of ProductsController which resolves to different IL. I believe that this is due to the class created to host the compiled lambda expression.
goofballLogic
One way around this would be to 'capture' the correct IL byte code from the running code and just matching that. The test would end up being very specific to the conditions being tested, but would work. This isn't bad if you have one test for that specific state, but I wouldn't want lots of them.
MarcLawrence