A Case in Favor of TDD: Transforming an Entity to DTO

Test Driven Development (TDD) is a great technique of coding, where we start with the tests and move forward with the production code. That means, the tests are the driven of our development, not our production code.

In this post, we are going to solve a typical development challenge using TDD:

Transforming an Entity to a DTO.

NOTE: You might think “Come on, that’s a trivial challenge to solve with TDD, let’s talk about something difficult please”. Well, yes, the challenge is trivial, but, as we will see, with a wrong strategy, the triviality could disappear and get us troubles. Moreover, you might have found this challenge in your projects, if not, in which cave do you live??

NOTE2: Why don’t we use a mapper framework like Orika or Dozer? well, I think, transformation is logic, that you should control and test. Of course, the context of the project defines if that logic is important to you or not, so, I suggest checking twice if you really need that kind of framework.

TRY IT YOURSELF: You can find the source code of this post here.

What is TDD?

Test Driven Development is a coding technique where we think first in the unit tests than the production code:

TDD process

That means, creating the unit tests that fail, then we must create the production code to get those tests pass, and finally refactoring, and do the process again, until your requirements are fulfill.

Transforming an Entity to a DTO

Well, we have a Person entity and a PersonDTO class. The idea is to transform one to another.

Transforming a Person to a PersonDTO

Let’s start with the Person entity:

class Person {
  private String name;
  private String address;
  private LocalDate birthDate;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getAddress() {
    return address;
  }

  public void setAddress(String address) {
    this.address = address;
  }

  public LocalDate getBirthDate() {
    return birthDate;
  }

  public void setBirthDate(LocalDate birthDate) {
    this.birthDate = birthDate;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name) &&
            Objects
                    .equals(address, person.address) &&
            Objects
                    .equals(birthDate,
                            person.birthDate);
  }

  ......
}

Now, let’s see the PersonDTO class:

class PersonDTO {
  private String name;
  private String address;
  private LocalDate birthDate;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getAddress() {
    return address;
  }

  public void setAddress(String address) {
    this.address = address;
  }

  public LocalDate getBirthDate() {
    return birthDate;
  }

  public void setBirthDate(LocalDate birthDate) {
    this.birthDate = birthDate;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    PersonDTO person = (PersonDTO) o;
    return Objects.equals(name, person.name) &&
            Objects
                    .equals(address, person.address) &&
            Objects
                    .equals(birthDate,
                            person.birthDate);
  }

  ....
}

TIP: Those classes look pretty similar each other. That’s a typical use case in our projects, we create data structures like DTO to decoupling the Entity from other layers like the view.

And finally, we create a PersonTransformer class to do the mapping:

class PersonTransformer {
  public PersonDTO transform(Person person){
    PersonDTO personDTO = new PersonDTO();
    personDTO.setAddress(person.getAddress());
    personDTO.setBirthDate(person.getBirthDate());
    personDTO.setName(person.getName());

    return personDTO;
  }
}

Of course, we create unit tests for this transformer:

public class PersonTransformerTest {
  private PersonTransformer personTransformer = new PersonTransformer();

  @Test
  public void transformFromPersonToPersonDTO(){
    Person person = new Person();
    person.setAddress("Street 1");
    person.setBirthDate(LocalDate.of(1980, 7, 4));
    person.setName("Daniel");

    PersonDTO personDTOExpected = new PersonDTO();
    personDTOExpected.setAddress("Street 1");
    personDTOExpected.setBirthDate(LocalDate.of(1980, 7, 4));
    personDTOExpected.setName("Daniel");

    PersonDTO personDTO = personTransformer
            .transform(person);

    assertThat(personDTO).isEqualTo(personDTOExpected);
  }
}

When we run the tests, everything looks fine:

Running PersonTransformerTest for the initial state

Now, we have a new requirement from our customer.

TRY IT YOURSELF: You can find the source code of this section here.

A New Requirement: Add a phone property

Our customer wants to see the phone number of the person, that means, we need to modify our current code to add the new phone field.

We are going to apply two strategies to code this new requirement:

  • No TDD strategy: We are going to add the field directly in the production code, thinking later about tests.
  • TDD strategy: We are going to start modifying the tests to fit the new requirement, and the production code when is required.

No TDD Strategy

Well, let’s see what we usually do if we do not apply TDD.

1. First, we add the new phone property with its setter and getter method, to the Person entity.

class Person {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  ....
}

2. Next, we add the new phone property with its setter and getter method, to the PersonDTO class.

class PersonDTO {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  ....
}

3. Now, we add the transformation missing in the PersonTransformer class.

class PersonTransformer {
  public PersonDTO transform(Person person){
    PersonDTO personDTO = new PersonDTO();
    personDTO.setAddress(person.getAddress());
    personDTO.setBirthDate(person.getBirthDate());
    personDTO.setName(person.getName());
    personDTO.setPhone(person.getPhone());

    return personDTO;
  }
}

4. Finally, we run our unit tests to see that everything is fine.

Running the unit tests for the no TDD strategy

Awesome, our unit tests passed without problem…… wait what??? we just added logic to our application and none unit test breaks, something is missing…. but, I am in a hurry, I won’t lose time checking the unit tests and see what is going on, at the end, they passed on green, so, my CI tool won’t break, and that’s the important part.

NOTE: If none unit tests failed, that means, we do not cover that part of the code with tests, and makes sense, because, we are adding code, so, that new code is not cover because doesn’t exist before. If we really concern about this, we should go to check what we are missing in the unit tests.

NOTE2: Note that when I said “cover”, I am not referring to test coverage, I am referring to code that is really tested. Those two are different things we can discuss in future posts.

TRY IT YOURSELF: You can find the source code of this section here.

A TDD approach

Well, let’s see how this problem is solved using TDD.

1. First, add the new phone field to the PersonTransformerTest.

package com.coderstower.blog.a_tdd_case_transforming_between_objects.addfield.tdd.step1;

import org.junit.Test;

import java.time.LocalDate;

import static org.assertj.core.api.Assertions.assertThat;

public class PersonTransformerTest {
  private PersonTransformer personTransformer = new PersonTransformer();

  @Test
  public void transformFromPersonToPersonDTO() {
    Person person = new Person();
    person.setAddress("Street 1");
    person.setBirthDate(LocalDate.of(1980, 7, 4));
    person.setName("Daniel");
    person.setPhone("111111");

    PersonDTO personDTOExpected = new PersonDTO();
    personDTOExpected.setAddress("Street 1");
    personDTOExpected
            .setBirthDate(LocalDate.of(1980, 7, 4));
    personDTOExpected.setName("Daniel");
    personDTOExpected.setPhone("22222");

    PersonDTO personDTO = personTransformer
            .transform(person);

    assertThat(personDTO).isEqualTo(personDTOExpected);
  }
}

NOTE: did you notice that the phones’ values for Person entity and PersonDTO don’t match? well, that is intentional, we shouldn’t solve the problem at the first try. TDD is a process that builds a solution in an incremental and iterative way.

Of course, this code doesn’t compile, the method setPhone doesn’t exist in the Person entity nor PersonDTO class. If we try to run that test, we get:

Running the tests got compilation problems

2. Now, we fix the compilation error. First, we go to Person entity and add the new setPhone method with its property.

class Person {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public void setPhone(String phone) {
    this.phone = phone;
  }

  ....
}

And we add the property to PersonDTO too.

class PersonDTO {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public void setPhone(String phone) {
    this.phone = phone;
  }
}

If we run the unit tests, we get now this:

Running the tests after fixing the compilation error, we got green

3. We shouldn’t get green tests, so, we are missing something. Let’s see carefully our unit tests:

public class PersonTransformerTest {
  private PersonTransformer personTransformer = new PersonTransformer();

  @Test
  public void transformFromPersonToPersonDTO() {
    Person person = new Person();

    ....

    PersonDTO personDTOExpected = new PersonDTO();
 
    ....

    PersonDTO personDTO = personTransformer
            .transform(person);

    assertThat(personDTO).isEqualTo(personDTOExpected);
  }
}

The assert uses isEqualTo method, which uses the equals method for our classes to compare, so, we need to add our new field to the equals method. First in the Person entity:

class Person {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public void setPhone(String phone) {
    this.phone = phone;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name) &&
            Objects
                    .equals(address, person.address) &&
            Objects.equals(birthDate,
                    person.birthDate) &&
            Objects.equals(phone, person.phone);
  }

  ....
}

And in the PersonDTO:

class PersonDTO {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public void setPhone(String phone) {
    this.phone = phone;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    PersonDTO personDTO = (PersonDTO) o;
    return Objects
            .equals(name, personDTO.name) &&
            Objects.equals(address,
                    personDTO.address) &&
            Objects.equals(birthDate,
                    personDTO.birthDate) &&
            Objects.equals(phone, personDTO.phone);
  }

  ....
}

Now, let’s try the tests again:

Test failure after updating the equals method by null phone

Awesome, the test failed. We are sending “222222” and “111111” in the test, however, we are getting a failure by a null phone. So, we are missing the transformation.

4. Adding the transformer code for phone field.

class PersonTransformer {
  public PersonDTO transform(
          Person person){
    PersonDTO personDTO = new PersonDTO();
    personDTO.setAddress(person.getAddress());
    personDTO.setBirthDate(person.getBirthDate());
    personDTO.setName(person.getName());
    personDTO.setPhone(person.getPhone());

    return personDTO;
  }
}

However, we need to add the phone getter in the classes too. First for the Person entity:

class Person {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name) &&
            Objects
                    .equals(address, person.address) &&
            Objects.equals(birthDate,
                    person.birthDate) &&
            Objects.equals(phone, person.phone);
  }

  ....
}

And for PersonDTO:

class PersonDTO {
  private String name;
  private String address;
  private LocalDate birthDate;
  private String phone;

  ....

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass())
      return false;
    PersonDTO personDTO = (PersonDTO) o;
    return Objects
            .equals(name, personDTO.name) &&
            Objects.equals(address,
                    personDTO.address) &&
            Objects.equals(birthDate,
                    personDTO.birthDate) &&
            Objects.equals(phone, personDTO.phone);
  }

  ....
}

Finally, if we run the tests:

Test failure after updating the equals method by wrong phone

Good, now we are not getting a null phone, we are getting the value we are sending on the unit test.

5. Fix the unit test data.

public class PersonTransformerTest {
  private PersonTransformer personTransformer = new PersonTransformer();

  @Test
  public void transformFromPersonToPersonDTO() {
    Person person = new Person();
    person.setAddress("Street 1");
    person.setBirthDate(LocalDate.of(1980, 7, 4));
    person.setName("Daniel");
    person.setPhone("111111");

    PersonDTO personDTOExpected = new PersonDTO();
    personDTOExpected.setAddress("Street 1");
    personDTOExpected
            .setBirthDate(LocalDate.of(1980, 7, 4));
    personDTOExpected.setName("Daniel");
    personDTOExpected.setPhone("111111");

    PersonDTO personDTO = personTransformer
            .transform(person);

    assertThat(personDTO).isEqualTo(personDTOExpected);
  }
}

There, we change the “222222” by “111111” phone in the expected person, so, when we run the unit test, we get:

Requirement complete with unit test on green

And we completed the TDD process and our requirement is fulfill.

TRY IT YOURSELF: You can find the source code of this section here.

Final Thought

We just compare the no TDD process with a TDD strategy, and as we can see, there were only one step more in the TDD strategy, however, if you paid attention, those additional steps were necessary, otherwise, our code will be incomplete, as it was in the no TDD approach.

TDD helps us to have a good unit tests set and minimal production code that fulfill our requirements.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

Advertisement

4 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s