DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

Trending

  • The Role of Retrieval Augmented Generation (RAG) in Development of AI-Infused Enterprise Applications
  • Ensuring Configuration Consistency Across Global Data Centers
  • Grafana Loki Fundamentals and Architecture
  • How to Build Local LLM RAG Apps With Ollama, DeepSeek-R1, and SingleStore
  1. DZone
  2. Coding
  3. Frameworks
  4. Testing DTOs and REST Controllers in Spring Boot

Testing DTOs and REST Controllers in Spring Boot

There are a few ways you can set up your own tests for Data Transfer Objects and REST Controllers in Spring Boot, removing the need to manually test everything yourself.

By 
Dan Newton user avatar
Dan Newton
·
Mar. 30, 17 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
90.3K Views

Join the DZone community and get the full member experience.

Join For Free

In this post, I will cover some tests that can be run to ensure that your DTOs (Data Transfer Objects) are being serialized and deserialized correctly, leading onto testing whether they are being passed to and returned from a REST Controller without errors.

Have a look at my previous posts Passing Data Transfer Objects with GET in Spring Boot and Returning Data Transfer Objects from a Rest Controller in Spring Boot for information about how the DTOs are passed to and returned from the REST Controller. Also have a look at Spring’s starter guide if you're starting from scratch. The setup, which is not described in this post, is covered there. 

The Maven dependencies required in this post are:

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>
</dependencies>


A little bit of context before we get to the tests.

The DTO:

public class PersonDTO {

    private String firstName;
    private String secondName;
    // Formats output date when this DTO is passed through JSON
    @JsonFormat(pattern = "dd/MM/yyyy")
        // Allows dd/MM/yyyy date to be passed into GET request in JSON
    @DateTimeFormat(pattern = "dd/MM/yyyy")
    private Date dateOfBirth;

    private String profession;
    private BigDecimal salary;

    public PersonDTO(
        String firstName, String secondName, Date dateOfBirth, String profession, BigDecimal salary) {
        this.firstName = firstName;
        this.secondName = secondName;
        this.dateOfBirth = dateOfBirth;
        this.profession = profession;
        this.salary = salary;
    }

    public PersonDTO() {}

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
}


The Controller:

@RestController
public class PersonRestController {

    @Autowired private ObjectMapper objectMapper;

    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");

    @RequestMapping("/getPersonDTO")
    public PersonDTO getPersonDTO(@RequestParam(value = "personDTO") String jsonPersonDTO)
    throws IOException {
        return getPersonDTOFromJson(jsonPersonDTO);
    }

    private PersonDTO getPersonDTOFromJson(final String jsonPersonDTO) throws IOException {
        return objectMapper.setDateFormat(simpleDateFormat).readValue(jsonPersonDTO, PersonDTO.class);
    }

    @RequestMapping("/getPersonDTOList")
    public List < PersonDTO > getPersonDTOList(
        @RequestParam(value = "personDTO") String jsonPersonDTO,
        @RequestParam(value = "personDTO2") String jsonPersonDTO2)
    throws IOException {
        final PersonDTO personDTO = getPersonDTOFromJson(jsonPersonDTO);
        final PersonDTO personDTO2 = getPersonDTOFromJson(jsonPersonDTO2);
        return Arrays.asList(personDTO, personDTO2);
    }

    @RequestMapping("/getPeopleDTO")
    public PeopleDTO getPeopleDTO(
        @RequestParam(value = "personDTO") String jsonPersonDTO,
        @RequestParam(value = "personDTO2") String jsonPersonDTO2)
    throws IOException {
        final PersonDTO personDTO = getPersonDTOFromJson(jsonPersonDTO);
        final PersonDTO personDTO2 = getPersonDTOFromJson(jsonPersonDTO2);
        return new PeopleDTO(Arrays.asList(personDTO, personDTO2));
    }
}


Let's start with the simplest test.

@SpringBootTest
@RunWith(SpringRunner.class)
public class PersonRestControllerTest {

    @Autowired
    private PersonRestController controller;

    @Test
    public void controllerInitializedCorrectly() {
        assertThat(controller).isNotNull();
    }
}


All this test does is check if the PersonRestController has been initialized. If it has not been loaded, then it will be null when it is injected in using the @Autowired annotation and lead to the test failing.

The next test will check if the PersonDTO will serialize and deserialize correctly:

@JsonTest
@RunWith(SpringRunner.class)
public class PersonDTOJsonTest {

    @Autowired private JacksonTester < PersonDTO > json;

    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(("dd/MM/yyyy"));

    private static final String FIRST_NAME = "First name";
    private static final String SECOND_NAME = "Second name";
    private static final String DATE_OF_BIRTH_STRING = "01/12/2020";
    private static final Date DATE_OF_BIRTH = parseDate(DATE_OF_BIRTH_STRING);
    private static final String PROFESSION = "Professional time waster";
    private static final BigDecimal SALARY = BigDecimal.ZERO;

    private static final String JSON_TO_DESERIALIZE =
        "{\"firstName\":\"" +
        FIRST_NAME +
        "\",\"secondName\":\"" +
        SECOND_NAME +
        "\",\"dateOfBirth\":\"" +
        DATE_OF_BIRTH_STRING +
        "\",\"profession\":\"" +
        PROFESSION +
        "\",\"salary\":" +
        SALARY +
        "}";

    private PersonDTO personDTO;

    private static Date parseDate(final String dateString) {
        try {
            return simpleDateFormat.parse(dateString);
        } catch (final ParseException e) {
            return new Date();
        }
    }

    @Before
    public void setup() throws ParseException {
        personDTO = new PersonDTO(FIRST_NAME, SECOND_NAME, DATE_OF_BIRTH, PROFESSION, SALARY);
    }

    @Test
    public void firstNameSerializes() throws IOException {
        assertThat(this.json.write(personDTO))
            .extractingJsonPathStringValue("@.firstName")
            .isEqualTo(FIRST_NAME);
    }

    @Test
    public void secondNameSerializes() throws IOException {
        assertThat(this.json.write(personDTO))
            .extractingJsonPathStringValue("@.secondName")
            .isEqualTo(SECOND_NAME);
    }

    @Test
    public void dateOfBirthSerializes() throws IOException, ParseException {
        assertThat(this.json.write(personDTO))
            .extractingJsonPathStringValue("@.dateOfBirth")
            .isEqualTo(DATE_OF_BIRTH_STRING);
    }

    @Test
    public void professionSerializes() throws IOException {
        assertThat(this.json.write(personDTO))
            .extractingJsonPathStringValue("@.profession")
            .isEqualTo(PROFESSION);
    }

    @Test
    public void salarySerializes() throws IOException {
        assertThat(this.json.write(personDTO))
            .extractingJsonPathNumberValue("@.salary")
            .isEqualTo(SALARY.intValue());
    }

    @Test
    public void firstNameDeserializes() throws IOException {
        assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getFirstName()).isEqualTo(FIRST_NAME);
    }

    @Test
    public void secondNameDeserializes() throws IOException {
        assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getSecondName()).isEqualTo(SECOND_NAME);
    }

    @Test
    public void dateOfBirthDeserializes() throws IOException {
        assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getDateOfBirth())
            .isEqualTo(DATE_OF_BIRTH);
    }

    @Test
    public void professionDeserializes() throws IOException {
        assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getProfession()).isEqualTo(PROFESSION);
    }

    @Test
    public void salaryDeserializes() throws IOException {
        assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getSalary()).isEqualTo(SALARY);
    }
}


Each property of the PersonDTO is tested for serialization and deserialization. If the serialization works correctly, then you can be sure that when you pass it to the REST controller in a JSON format, it will be received and converted to its proper object. You also want to ensure that the DTO can be deserialized correctly so it can be used when it is returned from the REST controller.

First, we mark the test with the @JsonTest annotation, which will disable full auto-configuration and only apply configuration relevant to JSON tests. This includes initializing the JacksonTester, which has been @Autowired into this test.

A closer look at one of the serialization tests:

@Test
public void firstNameSerializes() throws IOException {
  assertThat(this.json.write(personDTO))
      .extractingJsonPathStringValue("@.firstName")
      .isEqualTo(FIRST_NAME);
}


AssertJ is used in this test, as it provides useful methods for testing Spring applications. The JacksonTester converts the personDTO into JSON, and then the firstName property is extracted from it and compared to the expected value.

Now onto a deserialization test:

@Test
public void firstNameDeserializes() throws IOException {
    assertThat(this.json.parseObject(JSON_TO_DESERIALIZE).getFirstName()).isEqualTo(FIRST_NAME);
}


This test is basically the opposite of the previous test, but the configuration is almost the same. Instead of using json.write to serialize the object, json.parseObject turns the JSON into a PersonDTO.

The last test in this tutorial still tests serialization and deserialization, but also takes it a step further and involves the REST Controller.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(PersonRestController.class)
public class PersonRestControllerJsonTest {

    @Autowired private MockMvc mockMvc;

    private static final String PERSON_DTO_JSON =
        "{" +
        "\"firstName\":\"First name\"," +
        "\"secondName\":\"Second name\"," +
        "\"dateOfBirth\":\"01/12/2020\"," +
        "\"profession\":\"Professional time waster\"," +
        "\"salary\":0" +
        "}";

    private static final String PERSON_DTO_REQUEST_PARAMETER = "personDTO=" + PERSON_DTO_JSON;

    private static final String PERSON_DTO_2_JSON =
        "{" +
        "\"firstName\":\"Second Person First name\"," +
        "\"secondName\":\"Second Person Second name\"," +
        "\"dateOfBirth\":\"11/01/2017\"," +
        "\"profession\":\"Useless Person\"," +
        "\"salary\":0" +
        "}";

    private static final String PERSON_DTO_2_REQUEST_PARAMETER = "personDTO2=" + PERSON_DTO_2_JSON;

    private static final String GET_PERSON_DTO_LIST_JSON_TO_RETURN =
        "[ " + PERSON_DTO_JSON + "," + PERSON_DTO_2_JSON + "]";

    private static final String GET_PEOPLE_DTO_JSON_TO_RETURN =
        "{ people:[" + PERSON_DTO_JSON + "," + PERSON_DTO_2_JSON + "]}";

    @Test
    public void getPersonDTOReturnsCorrectJson() throws Exception {
        mockMvc
            .perform(get("/getPersonDTO?" + PERSON_DTO_REQUEST_PARAMETER))
            .andExpect(status().isOk())
            .andExpect(content().json(PERSON_DTO_JSON));
    }

    @Test
    public void getPersonDTOListReturnsCorrectJson() throws Exception {
        mockMvc
            .perform(
                get(
                    "/getPersonDTOList?" +
                    PERSON_DTO_REQUEST_PARAMETER +
                    "&" +
                    PERSON_DTO_2_REQUEST_PARAMETER))
            .andExpect(status().isOk())
            .andExpect(content().json(GET_PERSON_DTO_LIST_JSON_TO_RETURN));
    }

    @Test
    public void getPeopleDTOReturnsCorrectJson() throws Exception {
        mockMvc
            .perform(
                get(
                    "/getPeopleDTO?" +
                    PERSON_DTO_REQUEST_PARAMETER +
                    "&" +
                    PERSON_DTO_2_REQUEST_PARAMETER))
            .andExpect(status().isOk())
            .andExpect(content().json(GET_PEOPLE_DTO_JSON_TO_RETURN));
    }
}


The @WebMvcTest annotation is used, which will disable full auto-configuration and only apply configuration relevant to MVC tests, including setting up the MockMvc used in this test. The PersonRestController has been marked in the annotation, as it is the test subject. Using MockMvc provides a faster way of testing MVC controllers like the PersonRestController, as it removes the need to fully start an HTTP server.

@Test
public void getPersonDTOReturnsCorrectJson() throws Exception {
    mockMvc
        .perform(get("/getPersonDTO?" + PERSON_JSON_TO_DESERIALIZE))
        .andExpect(status().isOk())
        .andExpect(content().json(PERSON_JSON));
}


Each test takes in a string that represents the request that is being sent to the PersonRestController and then checks that the request was sent and returned successfully and that the retrieved JSON is correct. I have included some of the static imports that were used to make it a bit clearer where some of these methods are coming from.

The downside to the way I have written this test is that there is a lot of setup of fiddly strings to be used in the test cases. This problem could also be seen as a benefit, as it is clear what the input is and what the output should be, but this up to preference. Therefore, I have written the same test in a slightly different way, and you can decide which you prefer.

@RunWith(SpringRunner.class)
@WebMvcTest(PersonRestController.class)
public class PersonRestControllerJsonTestVersion2 {

    @Autowired private MockMvc mockMvc;

    @Autowired private ObjectMapper objectMapper;

    private JacksonTester < PersonDTO > personDTOJsonTester;
    private JacksonTester < List > listJsonTester;
    private JacksonTester < PeopleDTO > peopleDTOJsonTester;

    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(("dd/MM/yyyy"));

    private static final String PERSON_FIRST_NAME = "First name";
    private static final String PERSON_SECOND_NAME = "Second name";
    private static final String PERSON_DATE_OF_BIRTH_STRING = "01/12/2020";
    private static final Date PERSON_DATE_OF_BIRTH = parseDate(PERSON_DATE_OF_BIRTH_STRING);
    private static final String PERSON_PROFESSION = "Professional time waster";
    private static final BigDecimal PERSON_SALARY = BigDecimal.ZERO;

    private static final String PERSON_2_FIRST_NAME = "Second Person First name";
    private static final String PERSON_2_SECOND_NAME = "Second Person Second name";
    private static final String PERSON_2_DATE_OF_BIRTH_STRING = "11/01/2017";
    private static final Date PERSON_2_DATE_OF_BIRTH = parseDate(PERSON_2_DATE_OF_BIRTH_STRING);
    private static final String PERSON_2_PROFESSION = "Useless Person";
    private static final BigDecimal PERSON_2_SALARY = BigDecimal.ZERO;

    private static Date parseDate(final String dateString) {
        try {
            return simpleDateFormat.parse(dateString);
        } catch (final ParseException e) {
            return new Date();
        }
    }

    private PersonDTO personDTO;
    private PersonDTO personDTO2;

    @Before
    public void setup() {
        JacksonTester.initFields(this, objectMapper);
        personDTO =
            new PersonDTO(
                PERSON_FIRST_NAME,
                PERSON_SECOND_NAME,
                PERSON_DATE_OF_BIRTH,
                PERSON_PROFESSION,
                PERSON_SALARY);
        personDTO2 =
            new PersonDTO(
                PERSON_2_FIRST_NAME,
                PERSON_2_SECOND_NAME,
                PERSON_2_DATE_OF_BIRTH,
                PERSON_2_PROFESSION,
                PERSON_2_SALARY);
    }

    @Test
    public void getPersonDTOReturnsCorrectJson() throws Exception {
        final String personDTOJson = personDTOJsonTester.write(personDTO).getJson();
        final String personDTORequestParameter = "personDTO=" + personDTOJson;
        final String outputJson = personDTOJson;
        mockMvc
            .perform(get("/getPersonDTO?" + personDTORequestParameter))
            .andExpect(status().isOk())
            .andExpect(content().json(outputJson));
    }

    @Test
    public void getPersonDTOListReturnsCorrectJson() throws Exception {
        final String personDTORequestParameter =
            "personDTO=" + personDTOJsonTester.write(personDTO).getJson();
        final String personDTO2RequestParameter =
            "personDTO2=" + personDTOJsonTester.write(personDTO2).getJson();
        final String outputJson = listJsonTester.write(Arrays.asList(personDTO, personDTO2)).getJson();
        mockMvc
            .perform(
                get(
                    "/getPersonDTOList?" +
                    personDTORequestParameter +
                    "&" +
                    personDTO2RequestParameter))
            .andExpect(status().isOk())
            .andExpect(content().json(outputJson));
    }

    @Test
    public void getPeopleDTOReturnsCorrectJson() throws Exception {
        final String personDTORequestParameter =
            "personDTO=" + personDTOJsonTester.write(personDTO).getJson();
        final String personDTO2RequestParameter =
            "personDTO2=" + personDTOJsonTester.write(personDTO2).getJson();
        final String outputJson =
            peopleDTOJsonTester.write(new PeopleDTO(Arrays.asList(personDTO, personDTO2))).getJson();
        mockMvc
            .perform(
                get("/getPeopleDTO?" + personDTORequestParameter + "&" + personDTO2RequestParameter))
            .andExpect(status().isOk())
            .andExpect(content().json(outputJson));
    }
}


This version removes the need for setting up the JSON strings by using JacksonTester to convert objects to JSON. Setting up this test is a bit nicer as you do not need to write all JSON strings which might have errors in them, such as missing a single speech mark or square bracket. Although I believe this also has the disadvantage of being less clear as you cannot see what the JSON strings are as it has been abstracted away by the JacksonTester.

A few things about the code setup of the second version. As the @WebMvcTest annotation is being used the @JsonTest that was used earlier cannot be applied. This prevents the JacksonTester from being injected into the test using @Autowired. Therefore, to set it up:

JacksonTester.initFields(this, objectMapper);


JacksonTester.initFields(this, objectMapper); was added to the @Befere method that runs at the start of each test method. If both @WebMvcTest and @JsonTest are added at the same time the test will fail to run.

Conclusion

In conclusion, this tutorial has gone through a few methods that can be used to test Data Transfer Objects and REST Controllers in Spring Boot to help ensure that your application is running correctly and removes the need for you to manually test every single case yourself.

The code used in this post can be found on my GitHub.

REST Web Protocols Spring Framework Spring Boot Test data

Published at DZone with permission of Dan Newton, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot - How To Use Native SQL Queries | Restful Web Services
  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • RESTful Web Services: How To Create a Context Path for Spring Boot Application or Web Service
  • How To Validate HTTP Post Request Body - Restful Web Services With Spring Framework | Spring Boot

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!