# Simplifying Data Conversion: A Testable Approach for Entity Models and DTOs

In many applications, converting data from one format to another is a common task. For instance, you might need to convert a business entity object into JSON or vice versa. Many developers use extension methods like `ToJson()` or `ToModel()` for this conversion, which act on the JSON DTO object model or the entity.

For example, if we have the following business object:

```csharp
public class Person
{
    public Person(int id, string firstName, string lastName)
    {
        this.Id = id;
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public int Id { get; }

    public string FirstName { get; }

    public string LastName { get; }
}
```

And the JSON DTO version used to expose it as a Web API:

```csharp
public class PersonJson
{
    public PersonJson(int id, string firstName, string lastName)
    {
        this.Id = id;
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    [JsonPropertyName("id")]
    public int Id { get; }

    [JsonPropertyName("firstName")]
    public string FirstName { get; }

    [JsonPropertyName("lastName")]
    public string LastName { get; }
}
```

The extension method that allows converting from `Person` to `PersonJson` could look like this:

```csharp
public static class PersonJsonExtensions
{
    public static PersonJson ToJson(this Person person)
    {
        return new PersonJson(person.Id, person.FirstName, person.LastName);
    }
}
```

And the usage inside an ASP.NET Core controller is straightforward:

```csharp
[ApiController]
[Route("persons")]
public class PersonController : ControllerBase
{
    private readonly IPersonManager manager;

    public PersonController(IPersonManager manager)
    {
        this.manager = manager;
    }

    [HttpGet("byId/{id}")]
    public PersonJson GetById(int id)
    {
        var person = this.manager.GetByID(id);
        var personJson = person.ToJson();

        return personJson;
    }

    [HttpGet("byName/{name}")]
    public PersonJson GetByName(string name)
    {
        var person = this.manager.GetByName(name);
        var personJson = person.ToJson();

        return personJson;
    }
}
```

However, this approach can complicate unit testing. In this article, I'll explain an alternative method using an interface to decouple the code, which makes testing easier.

## The problem with extension methods

While this approach works, the problem arises during unit testing. You need to test both the code that retrieves the business entity (the `GetById()` or `GetByName()` methods) and the code that converts it to JSON. This makes your tests more complex and harder to maintain.

For example, to test our `PersonController`, we would write the following unit tests:

```csharp
public class PersonControllerTest
{
    [Fact]
    public void GetById()
    {
        // Arrange
        var person = new Person(1, "John", "DOE");

        var manager = new Mock<IPersonManager>(MockBehavior.Strict);
        manager.Setup(m => m.GetByID(1234))
            .Returns(person);

        var controller = new PersonController(manager.Object);

        // Act
        var json = controller.GetById(1234);

        json.FirstName.Should().Be("John");
        json.Id.Should().Be(1);
        json.LastName.Should().Be("DOE");

        // Assert
        manager.VerifyAll();
    }

    [Fact]
    public void GetByName()
    {
        // Arrange
        var person = new Person(1, "John", "DOE");

        var manager = new Mock<IPersonManager>(MockBehavior.Strict);
        manager.Setup(m => m.GetByName("Someone"))
            .Returns(person);

        var controller = new PersonController(manager.Object);

        // Act
        var json = controller.GetByName("Someone");

        // Assert
        json.FirstName.Should().Be("John");
        json.Id.Should().Be(1);
        json.LastName.Should().Be("DOE");

        manager.VerifyAll();
    }
}
```

As you can see, in these unit tests, for each use of the `ToJson()` extension method, we have to test the mapping of each property. These unit tests are quite simple, but imagine a controller that makes many different calls to the `ToJson()` extension method, especially if the `Person`/`PersonJson` objects have many properties.

## The solution: Create an external mapper

To simplify testing, we can move the conversion logic into a separate class by introducing a simple interface called `IPersonJsonMapper`. Here's how:

1. Define the interface:
    
    ```csharp
    public interface IPersonJsonMapper
    {
        PersonJson ToJson(Person person);
    }
    ```
    
2. Implement the interface:
    
    ```csharp
    public class PersonJsonMapper : IPersonJsonMapper
    {
        public PersonJson ToJson(Person person)
        {
            return new PersonJson(person.Id, person.FirstName, person.LastName);
        }
    }
    ```
    
3. Use the interface in our controller:
    
    ```csharp
    [ApiController]
    [Route("persons")]
    public class PersonController : ControllerBase
    {
        private readonly IPersonManager manager;
    
        private readonly IPersonJsonMapper jsonMapper;
    
        public PersonController(IPersonManager manager, IPersonJsonMapper jsonMapper)
        {
            this.manager = manager;
            this.jsonMapper = jsonMapper;
        }
    
        [HttpGet("byId/{id}")]
        public PersonJson GetById(int id)
        {
            var person = this.manager.GetByID(id);
            var personJson = this.jsonMapper.ToJson(person);
    
            return personJson;
        }
    
        [HttpGet("byName/{name}")]
        public PersonJson GetByName(string name)
        {
            var person = this.manager.GetByName(name);
            var personJson = this.jsonMapper.ToJson(person);
    
            return personJson;
        }
    }
    ```
    
4. Register the mapper as service in the `Program.cs` file:
    

```csharp
builder.Services.AddSingleton<IPersonJsonMapper, PersonJsonMapper>();
```

The code of the controller remains as simple as when using the `ToJson()` extension method, but now the unit test becomes easier:

```csharp
public class PersonControllerTest
{
    [Fact]
    public void GetById()
    {
        // Arrange
        var person = new Person(default, default, default);
        var personJson = new PersonJson(default, default, default);

        var manager = new Mock<IPersonManager>(MockBehavior.Strict);
        manager.Setup(m => m.GetByID(1234))
            .Returns(person);

        var mapper = new Mock<IPersonJsonMapper>(MockBehavior.Strict);
        mapper.Setup(m => m.ToJson(person))
            .Returns(personJson);

        var controller = new PersonController(manager.Object, mapper.Object);

        // Act
        var json = controller.GetById(1234);

        // Assert
        json.Should().BeSameAs(personJson);

        manager.VerifyAll();
        mapper.VerifyAll();
    }

    [Fact]
    public void GetByName()
    {
        // Arrange
        var person = new Person(default, default, default);
        var personJson = new PersonJson(default, default, default);

        var manager = new Mock<IPersonManager>(MockBehavior.Strict);
        manager.Setup(m => m.GetByName("Someone"))
            .Returns(person);

        var mapper = new Mock<IPersonJsonMapper>(MockBehavior.Strict);
        mapper.Setup(m => m.ToJson(person))
            .Returns(personJson);

        var controller = new PersonController(manager.Object, mapper.Object);

        // Act
        var json = controller.GetByName("Someone");

        // Assert
        json.Should().BeSameAs(personJson);

        manager.VerifyAll();
        mapper.VerifyAll();
    }
}
```

As you can see, now we don't need to assert the content of the `PersonJson` object because it's not the controller's responsibility. Also, I intentionally instantiate the `Person` and `PersonJson` objects with default values, since the code being tested will not use them. Of course, we could use a library like [AutoFixture](https://github.com/AutoFixture/AutoFixture) to easily instantiate these objects.

```csharp
var person = new Person(default, default, default);
var personJson = new PersonJson(default, default, default);
```

### Unit test the mapper

Even though we don't need to assert the content of the `PersonJson` instance in the controllers, we should still assert the conversion from a `Person` instance to a `PersonJson` by adding a unit test for the `PersonJsonMapper` class:

```csharp
public class PersonJsonMappingTest
{
    [Fact]
    public void ToJson()
    {
        // Arrange
        var person = new Person(1234, "John", "DOE");

        var mapper = new PersonJsonMapper();

        // Act
        var json = mapper.ToJson(person);

        // Assert
        json.FirstName.Should().Be("John");
        json.Id.Should().Be(1234);
        json.LastName.Should().Be("DOE");
    }
}
```

### Change the implementation of the mapper

By delegating the conversion to a dedicated class, we can also easily change the implementation using another mapper library, such as [AutoMapper](https://docs.automapper.org/).

Using this approach allows you to change the library or the mapping logic easily without altering the code in the controller.

## Conclusion

Delegating data conversion to another class makes your code more testable and maintainable. This approach can be applied at various levels, whether in controllers to convert XML/JSON to entities or in the data access layer for converting Entity Framework entities to business entities. By using this approach, you also respect the "Single Responsibility" principle of the [S.O.L.I.D. principles](https://en.wikipedia.org/wiki/SOLID).

A complete example of the source of this article is available within one of my public GitHub projects: GillesTourreau/[GillesTourreau.ModelMapper](https://github.com/GillesTourreau/GillesTourreau.ModelMapper)[: Example project to delegate the conversion of entity model to JSON in other class (github.com).](https://github.com/GillesTourreau/GillesTourreau.ModelMapper)
