Apr 12, 2010

Learning Tests

Robert C. Martin brought it into my world in his book “Clean Code – A Handbook of Agile Software Craftmanship” - “Learning tests”.

“Learning tests” (by K. Beck?)

  • are unit tests
  • to explore (learn) a new API or framework
  • that builds a set of tests for regression when a new version of the component is used to validate that your expectations of the API is still fulfilled

For an upcoming training I wanted to use AutoMapper (by J. Bogard) for entity to/from DTO transformations. There is not a lot of documentation around for this open source library and my first trial with the API ended in a strange exception.

So I decided to write some unit tests first (getting them red was rather easy ;-) and investigate on the common use of AutoMapper (www.google.com/codesearch).

Here are some of my tests (please note the method names which should give you an idea about my expectations):

/// <summary>

/// Learning tests for automapper by Jimmy Bogard from http://automapper.codeplex.com/

/// </summary>

[TestClass]

public class AutoMapperTests

{

    [TestInitialize]

    public void TestInitialize()

    {

        Mapper.Reset();

    }

 

    [TestMethod]

    [ExpectedException(typeof(AutoMapperMappingException))]

    public void Map_NoMappingExists_Throws()

    {

        DestinationType destination = Mapper.Map<SourceType, DestinationType>(SourceType.CreateSimple());

    }

 

    [TestMethod]

    public void Map_CreateMap_ReturnsMappingExpression()

    {

        IMappingExpression<SourceType, DestinationType> mappingExpression = Mapper.CreateMap<SourceType, DestinationType>();

 

        DestinationType destination = Mapper.Map<SourceType, DestinationType>(SourceType.CreateSimple());

 

        Assert.IsInstanceOfType(destination, typeof(DestinationType), "must return instance of destination type");

    }

 

    [TestMethod]

    public void Map_CreateMapForOneMember_ReturnsDestinationWithOneMember()

    {

        Mapper

            .CreateMap<SourceType, DestinationType>()

            .ForMember(d => d.Text, c => c.MapFrom<string>(s => s.Text));

        SourceType source = SourceType.CreateSimple();

 

        DestinationType destination = Mapper.Map<SourceType, DestinationType>(source);

 

        Assert.AreEqual<string>(source.Text, destination.Text, "member must be mapped");

    }

 

    [TestMethod]

    public void Map_CreateMapForMembersIgnoreOne_ReturnsDestinationWithoutMember()

    {

        Mapper

            .CreateMap<SourceType, DestinationType>()

            .ForMember(d => d.Text, c => c.MapFrom<string>(s => s.Text))

            .ForMember(d => d.Floating, c => c.Ignore())

            .ForMember(d => d.Number, c => c.MapFrom<int>(s => s.Number));

        SourceType source = SourceType.CreateSimple();

 

        DestinationType destination = Mapper.Map<SourceType, DestinationType>(source);

 

        Assert.AreNotEqual<double>(source.Floating, destination.Floating, "ignored member must not be mapped");

    }

 

    [TestMethod]

    public void Map_CreateMapForChildMembers_ReturnDestinationWithChildMembers()

    {

        Mapper

            .CreateMap<SourceType, DestinationType>()

            .ForMember(d => d.Child, c => c.MapFrom<SourceType>(s => s.Child));

        SourceType source = SourceType.CreateSimple();

        source.Child = SourceType.CreateSimple();

 

        DestinationType destination = Mapper.Map<SourceType, DestinationType>(source);

 

        Assert.AreEqual<string>(source.Child.Text, destination.Child.Text, "child member (reference type) not mapped");

    }

 

    [TestMethod]

    [ExpectedException(typeof(AutoMapperMappingException))]

    public void Map_CreateMapWithoutRecursiveMaps_Throws()

    {

        // map "outer" (root) type

        Mapper.CreateMap<TopSourceType, TopDestinationType>()

            .ForMember(d => d.DestinationType, c => c.MapFrom<SourceType>(s => s.SourceType));

 

        TopSourceType source = TopSourceType.CreateSimple();

        TopDestinationType destination = Mapper.Map<TopSourceType, TopDestinationType>(source);

 

        Assert.Fail("inner (reference type) map not done. Automapper must throw");

    }

 

    [TestMethod]

    public void Map_CreateMapWithRecursiveMaps_ReturnsTreeDestination()

    {

        // map "inner" type

        Mapper.CreateMap<SourceType, DestinationType>()

            .ForMember(d => d.Text, c => c.MapFrom<string>(s => s.Number.ToString(CultureInfo.CurrentCulture)));

 

        // map "outer" (root) type

        Mapper.CreateMap<TopSourceType, TopDestinationType>()

            .ForMember(d => d.DestinationType, c => c.MapFrom<SourceType>(s => s.SourceType));

 

        TopSourceType source = TopSourceType.CreateSimple();

        TopDestinationType destination = Mapper.Map<TopSourceType, TopDestinationType>(source);

 

        Assert.AreEqual<string>(

            source.SourceType.Number.ToString(CultureInfo.CurrentCulture),

            destination.DestinationType.Text,

            "child member (of different reference types) not mapped");

    }

 

    // … and more

}

And here are my tests classes:

/// <summary>

/// Tests-only source type for mapping tests.

/// </summary>

internal class SourceType

{

    public int Number { get; set; }

    public string Text { get; set; }

    public double Floating { get; set; }

 

    public SourceType Child { get; set; }

 

    public static SourceType CreateSimple()

    {

        return new SourceType { Floating = 1.1, Number = 13, Text = Guid.NewGuid().ToString() };

    }

}

 

internal class TopSourceType

{

    public int? Id { get; set; }

    public SourceType SourceType { get; set; }

 

    public static TopSourceType CreateSimple()

    {

        return new TopSourceType { Id = 13, SourceType = SourceType.CreateSimple() };

    }

}

 

/// <summary>

/// Tests-only destination type for mapping tests.

/// </summary>

internal class DestinationType

{

    public int Number { get; set; }

    public string Text { get; set; }

    public double Floating { get; set; }

 

    public DestinationType Child { get; set; }

 

    public override string ToString()

    {

        return string.Format(CultureInfo.CurrentCulture, "Number = {0}, Text = {1}, Floating = {2}", this.Number, this.Text, this.Floating);

    }

}

 

internal class TopDestinationType

{

    public string Id { get; set; }

    public DestinationType DestinationType { get; set; }

}