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; }
}