tags:

views:

520

answers:

2

I have some basic classes with cloning methods:

public class SimpleClass
{
    public int ValueA { get; set; }

    public string ValueB { get; set; }

    public ulong ValueC { get; set; }

    public SimpleClass TypedClone()
    {
        var item = new SimpleClass
        {
            ValueA = this.ValueA,
            ValueB = this.ValueB,
            ValueC = this.ValueC 
        };

        return item;
    }
}

I want a unit test that will tell me if I add ValueD, but forget to add it to the Clone method. My first attempt was to use Moq and its VerifyGet method to make sure that each property was accessed.

    public void GenericCloneTest()
    {
        var mock = new Mock<SimpleClass>();
        var c = mock.Object.GenericClone();
        var properties = typeof(SimpleClass).GetProperties();

        foreach (var property in properties)
        {
            var expression = Expression.Property(
                Expression.Parameter(typeof(SimpleClass), "c"), 
                property);

            var type = Expression.GetFuncType(typeof (SimpleClass),  
                property.PropertyType);

            var actionExpression = Expression.Lambda(type, expression,
                Expression.Parameter(typeof(SimpleClass), "c"));

            mock.VerifyGet<object>
                ((Expression<Func<SimpleClass,object>>)actionExpression);
        }
    }

This doesn't work because the VerifyGet method needs to know the return type of the Property accessor, and I can't figure out any way to insert it at runtime (you'll notice my lame attempt to use "object" which crashed and burned).

I'm not even sure using Moq is a good idea, it was just my first one.

UPDATE: With no quick and easy generic way to test a cloning method I settled on writing type-specific tests for each class. This still leaves me with the problem of knowing when properties have been added. I settled on appending this to my clone unit tests:

      var signature = typeof (Connection)
            .GetProperties()
            .Select(p => p.Name)
            .Aggregate(
                new StringBuilder(), 
                (builder, name) =>
                    builder.Append(name)).ToString();

      Assert.AreEqual(
           "DataSessionStateDataTechnologyBytesReceivedBytesSentDuration",
           signature);

If I add a property the test will fail. It still depends on me being responsible enough to fix the rest of the test when the signature match fails.

+1  A: 

One of the simplest ways to ensure you get all of your fields is to simply use the MemberwiseClone() method. That will automatically copy all of your classes fields to the new instance.

Paul Alexander
Thanks Paul! I always figured there had to be something built in to .NET like this, but hadn't found it yet. This will be useful anywhere I want to make a quick shallow copy.
MichaC
+1  A: 

You don't really want to use Moq here, you just need to use some reflection. Basically write a test that create an instance of your object, sets each property, clones it, and then makes sure that each property is equal on the clone.

Like Paul said, check out MemberwiseClone() that basically does this for you, or at least implement ICloneable which is a standard practice for how to do this. (MemberwiseClone() only does a shallow copy which is why you might need to implement ICloneable). Then you could even write a test that looks through your assembly for everything that implements ICloneable and tests it.

James Avery
I started thinking about it using just reflection, but the hard part is the step where you "set each property." I have to set each property to something other than the default value to verify the clone method, but I have a collection of properties with any number of types. I have to write code that knows how to change and compare any type it might encounter. The question is whether that is more work than just writing a unique clone test for each type.
MichaC
Yeah, its a balance of complexity in one test over lots of extra code in multiple tests... always a tough judgment call. In this case I would go for one complex test though since it will save you lots of repetitive testing. You could build up a simple dictionary or switch statement with various test values for the different data types.
James Avery