Click here to Skip to main content
13,249,813 members (86,783 online)
Click here to Skip to main content
Add your own
alternative version

Stats

6.3K views
18 bookmarked
Posted 9 Feb 2016

Generic Mapper written in TDD

, 9 Feb 2016
Rate this:
Please Sign up or sign in to vote.
This article shows how to easily write own simple generic mapper with TDD. This is a great excercise to practice TDD that results in fully-working and simple generic mapper that can be used in production projects.

Introduction

The article shows step by step how to write own mapper.
Using tests-first approach makes it easier to understand what this mapper actually does and how to implement complex features from scratch.

Background

What are mappers and why use them?

Mappers are used to rewrite data from one object to another. Often these objects are the same Business object, they just belong to different application layers.

In simple and small applications mappers are often not needed. Rewriting data from one object to another is done manually, as usually these objects are completly different. E.g. :
 

var email = new Email
{
    From = user.Name,
    Topic = topicTxt.Text,
    Body = bodyTxt.Text,
    Date = DateTime.Now
};


But in larger applications with more layers of abstraction there are many different classes that represent the same business entity. This can be seen when there is a combination of Data Access Layer (entities in EntityFramework), Data Transaction Objects (objects passed by the WCF) or ViewModels (objects displayed in the View layer, for example in the MVC applications). Application divided in these layers can look like this:

var customerDto = new CustomerDto
{
    Id = customerEntity.Id,
    Name = customerEntity.Name,
    Surname = customerEntity.Surname
};

var customerViewModel = new CustomerViewModel
{
    Id = customerDto.Id,
    Name = customerDto.Name,
    Surname = customerDto.Surname
};

Of course with help come constructors that accept another object or factory methods. But still there will be some initialization in the code.

A mapper could do this "magically" :) 

var customerDto = mapper.Map<CustomerDto>(customerEntity);

var customerViewModel = mapper.Map<CustomerViewModel>(customerDto);

Let this mindless and stupid work be done automatically so we could save our time for real problems ;)

 

TDD stands for Test Driven Development. The idea is to write the Unit Test firstly, then to write implementation that would satisfy the test.
Writing tests is a big advantage. Makes the code resistant to modifications. It also saves time in testing the application, especially when there are many paths to check or when testing the application is time-consuming.

In my opinion a TDD has another great benefits.
Sometimes the result of the operation is simple, but the implementation is vague and at first sight it is very difficult to write the code.
Another benefit, especially when it comes to API is that these Unit Tests are basic usage of the API. This means that when writing the tests it is already known how this API would be used. I think that there are many tools, that makes a lot of great stuff, but they are realy hard and unclear to use. In other words: if you're not a user of the API you don't care how somebody would use it. And it is wrong :)

Using the code

For those who are not familiar with TDD or even with Unit Tests, our journey will start by creating a new project.

In Visual Studio select File -> New...
In the New Project window search for Unit Test Project (can be found in Templates -> Visual C# -> Test).
Type the name for the project (e.g. SandboxTests) and create the project.

Now create a new Unit-Tests class. 
Right click on the project, select Add -> Unit Test...
Then change the name of the file to GenericMapperTests.cs

Visual Studio automatically prepared all references necessary for writing Unit Tests. So..

Let's write our first Unit Test.

[TestClass]
public class GenericMapperTests
{
    [TestMethod]
    public void ShouldMapPropertiesFromOneObjectToAnother()
    {
        // Given

        // When

        // Then
    }
}

What would we like to do is to create and assign one object. Then create the same object and rewrite values from the first one to the second.

Complete test should look like this:

[TestMethod]
public void ShouldMapPropertiesFromOneObjectToAnother()
{
    // Given
    var customer = new Customer
    {
        Id = 1,
        Name = "Miłosz",
        Surname = "Wieczorek"
    };
    var newCustomer = new Customer();
    var mapper = new GenericMapper();

    // When
    mapper.Map(customer, newCustomer);

    // Then
    Assert.AreEqual(customer.Id, newCustomer.Id, "Id");
    Assert.AreEqual(customer.Name, newCustomer.Name, "Name");
    Assert.AreEqual(customer.Surname, newCustomer.Surname, "Surname");
}

Writing this test we have generated some basic object Customer and our destination - GenericMapper class.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
}

public class GenericMapper
{
    public void Map(Customer customer, Customer newCustomer)
    {
    }
}

As the title of this article says, it should be a Generic Mapper. So let's fix the method signature.
By the way let's make some first implementation that satisfies the test.

public class GenericMapper
    {
        public void Map<T>(T from, T to)
        {
            var type = typeof(T);
            var properties = type.GetProperties();
            foreach (var property in properties)
            {
                property.SetValue(to, property.GetValue(from));
            }
        }
    }

There is no rocket science, but it is a basic implementation that satisfies our test.

Now the Mapper maps properties from one object to another, but these objects need to be the same type. Let's improve a little bit our Mapper so it could map values from one type to another, as it is a real objective of the Mapper.

As we know what the Mapper should do, writing second test is pretty obvious. 

[TestMethod]
public void ShouldMapPropertiesFromOneObjectToAnotherWithDifferentTypes()
{
    // Given
    var customer = new Customer
    {
        Id = 1,
        Name = "Miłosz",
        Surname = "Wieczorek"
    };
    var newCustomer = new CustomerDto();
    var mapper = new GenericMapper();

    // When
    mapper.Map(customer, newCustomer);

    // Then
    Assert.AreEqual(customer.Id, newCustomer.Id, "Id");
    Assert.AreEqual(customer.Name, newCustomer.Name, "Name");
    Assert.AreEqual(customer.Surname, newCustomer.Surname, "Surname");
}

By the way we have created our new object, a CustomerDto that looks entirely the same as Customer.

public class CustomerDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
}

Unfortunately these changes generates compilation error as the Map method in the Mapper doesn't allow to pass two different types.

Let's improve the Map method implementation so our two tests would pass.

public void Map<TFrom, TResult>(TFrom from, TResult to)
{
    var typeFrom = typeof(TFrom);
    var typeTo = typeof(TResult);
    var propertiesFrom = typeFrom.GetProperties();
    var propertiesTo = typeTo.GetProperties();

    foreach (var propFrom in propertiesFrom)
    {
        foreach (var propTo in propertiesTo)
        {
            if (propTo.Name == propFrom.Name &&
                propTo.PropertyType == propFrom.PropertyType)
            {
                propTo.SetValue(to, propFrom.GetValue(from));
            }
        }
    }
}

Now run tests and... Yea our two tests are green :) 
But this code is not so pretty... Iterating through two nested collections... It doesn't look good :) Let's do some refactoring and use of LINQ.

public void Map<TFrom, TResult>(TFrom from, TResult to)
{
    var typeFrom = typeof(TFrom);
    var typeTo = typeof(TResult);
    var properties = typeFrom.GetProperties()
        .Join(typeTo.GetProperties(), f => f.Name, t => t.Name, (f, t) => new
        {
            propFrom = f,
            propTo = t
        });

    foreach (var prop in properties.Where(p => p.propFrom.PropertyType == p.propTo.PropertyType))
    {
        prop.propTo.SetValue(to, prop.propFrom.GetValue(from));
    }
}

Now that's better.

Thanks to Unit Tests we can easily check if our refactoring doesn't break the Map method.

Tests are green, so let's improve the Mapper :)


Basic Mapper implementation is working fine, now let's think what features should our Mapper have.
I would like my Mapper to not write to read-only fields. I would also like to achieve the same effect in the opposite situation: a value should not be rewritten from the write-only field.

Let's prepare the test.

[TestMethod]
public void ShouldNotMapReadonlyAndWriteOnlyFields()
{
    // Given
    var customer = new Customer
    {
        Id = 1,
        DateOfBirth = new DateTime(1990, 01, 01),
        Age = 26,
        UpdatedBy = 15
    };

    var customerDto = new CustomerDto();
    var mapper = new GenericMapper();

    // When
    mapper.Map(customer, customerDto);

    // Then
    Assert.AreEqual(customer.DateOfBirth, customerDto.DateOfBirth, "Date of birth");
    Assert.AreNotEqual(customer.Age, customerDto.Age, "Age");
    Assert.AreEqual(default(int), customerDto.UpdatedBy, "UpdatedBy");
    Assert.AreNotEqual(customer.GetUpdatedBy(), customerDto.UpdatedBy, "Age");
}

Here are updated objects of Customer and CustomerDto. 

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }

    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }

    public int UpdatedBy
    {
        private get;
        set;
    }

    public int GetUpdatedBy()
    {
        return UpdatedBy;
    }
}

public class CustomerDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }

    public DateTime DateOfBirth { get; set; }

    public int Age
    {
        get;
        private set;
    }

    public int UpdatedBy { get; set; }
}

And now let's make the test green :)

public void Map<TFrom, TResult>(TFrom from, TResult to)
{
    var typeFrom = typeof(TFrom);
    var typeTo = typeof(TResult);
    var properties = typeFrom.GetProperties()
        .Join(typeTo.GetProperties(), f => f.Name, t => t.Name, (f, t) => new
        {
            propFrom = f,
            propTo = t
        });

    foreach (var prop in properties.Where(p => p.propFrom.PropertyType == p.propTo.PropertyType && p.propFrom.CanRead && p.propTo.CanWrite && p.propFrom.GetMethod.IsPublic && p.propTo.SetMethod.IsPublic))
    {
        prop.propTo.SetValue(to, prop.propFrom.GetValue(from));
    }
}

The test is green, but the code looks ugly... A little refactor is necessary :)

public class GenericMapper
{
    public void Map<TFrom, TResult>(TFrom from, TResult to)
    {
        var typeFrom = typeof(TFrom);
        var typeTo = typeof(TResult);
        var properties = typeFrom.GetProperties()
            .Join(typeTo.GetProperties(), f => f.Name, t => t.Name, (f, t) => new
            {
                propFrom = f,
                propTo = t
            });

        foreach (var prop in properties.Where(p => CanRewriteValue(p.propFrom, p.propTo)))
        {
            prop.propTo.SetValue(to, prop.propFrom.GetValue(from));
        }
    }

    private bool CanRewriteValue(PropertyInfo propFrom, PropertyInfo propTo)
    {
        return propFrom.PropertyType == propTo.PropertyType &&
            propFrom.CanRead &&
            propTo.CanWrite &&
            propFrom.GetMethod.IsPublic &&
            propTo.SetMethod.IsPublic;
    }
}

Much better :)

OK so we have nice and working Generic Mapper. But as we can see in our Unit Tests, this Mapper is very inconvenient to use it. 
First of all, I would like to make it static. This would affect existing tests, but it would be a small change.
Secondly, I would like my Mapper to create new instance of object with already mapped properties. For this a new test is necessary.

I think these would be great features that would make our Mapper be more useful.

New test with creation of an object.

[TestMethod]
public void ShouldCreateNewObjectWithAlreadyMappedProperties()
{
    // Given
    var customer = new Customer
    {
        Id = 4,
        Name = "John",
        Surname = "Doe"
    };

    // When
    var newCustomer = GenericMapper.Create<Customer, CustomerDto>(customer);

    // Then
    Assert.AreEqual(customer.Id, newCustomer.Id, "Id");
    Assert.AreEqual(customer.Name, newCustomer.Name, "Name");
    Assert.AreEqual(customer.Surname, newCustomer.Surname, "Surname");
}

And implementation. Pretty simple, but very usefull.

public static TResult Create<TFrom, TResult>(TFrom source) where TResult : class, new()
{
    var result = new TResult();
    Map(source, result);
    return result;
}

That's it! 

Simple Generic Mapper with a few Unit Tests. Can be easily moved to another project as we are awared what it does, how it behaves and so on.

The Generic Mapper can be extended with many functionalities. All that is needed is to write a test, then implementation that satisfies the test. After all some refactor maybe :) 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Manfredzik
Software Developer
Poland Poland
Hello from Poland Wink | ;)

I have been a .NET Developer since 2012. Since then I have had an opportunity to work with many projects written in technologies such as WinForms, WPF, ASP.NET MVC and ASP.NET WebAPI.
Ocasionally I did some projects in my spare time (for fun, on the studies project or as a remote part-time job).
But first of all I love coding and the possibility to increase my programming skills.

For few years I have been into client-side coding in JavaScript or TypeScript with different frameworks. I try to be up to date with latest trendings but actually the client side world is moving so fast I barerly can keep up with it Smile | :)

Cheers,
Miłosz

You may also be interested in...

Comments and Discussions

 
Suggestionperformance? Pin
Irina Pykhova6-Mar-16 3:05
professionalIrina Pykhova6-Mar-16 3:05 
QuestionMap Method for Beginner Pin
Anjum.Rizwi10-Feb-16 19:53
professionalAnjum.Rizwi10-Feb-16 19:53 
SuggestionAn issue about testing - compare against absolute Pin
John Brett10-Feb-16 7:09
memberJohn Brett10-Feb-16 7:09 
AnswerRe: An issue about testing - compare against absolute Pin
Manfredzik11-Feb-16 1:29
memberManfredzik11-Feb-16 1:29 
GeneralRe: An issue about testing - compare against absolute Pin
John Brett11-Feb-16 2:23
memberJohn Brett11-Feb-16 2:23 
QuestionAn Alternative Map Method Pin
George Swan10-Feb-16 0:24
memberGeorge Swan10-Feb-16 0:24 
AnswerRe: An Alternative Map Method Pin
Manfredzik11-Feb-16 3:15
memberManfredzik11-Feb-16 3:15 
GeneralRe: An Alternative Map Method Pin
George Swan11-Feb-16 4:02
memberGeorge Swan11-Feb-16 4:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171114.1 | Last Updated 9 Feb 2016
Article Copyright 2016 by Manfredzik
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid