# Testing with Spring Boot

In my previous article, we created a Spring Boot REST API [application](https://techwithmaddy.com/how-to-create-a-spring-boot-rest-api), but I didn't include the tests. In the real world, we would have to write them, so in this article, I will show how to write Unit Tests on a Spring Boot application.

## What Is Unit Testing?

Unit Testing is how software engineers can test a single piece of code in a codebase. It's a type of test that allows developers to identify problems at an earlier stage.

## What Is Integration Testing?

Integration Testing is a type of testing used to ensure that all the integration points are working fine and each system is interacting with each other correctly.

Let's start by updating the pom.xml by adding the relevant dependencies.

```xml
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>5.1.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.assertj</groupId>
			<artifactId>assertj-core</artifactId>
			<version>3.6.2</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>test</scope>
		</dependency>
```

We added:

* **Junit5**: JUnit is a Java library that allows you to write and run automated tests.
    
* **AssertJ**: AssertJ is a Java library that allows us to write assertion statements.
    
* **H2 database**: this is a Java in-memory relational database management system mainly used for testing purposes.
    

Mockito is going to be added manually.

This is the package structure under the test folder.

![test-package-structure.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1638811672579/UQe0aWOxr.png align="center")

### Creating the Abstract Test Class

```java
package com.techwithmaddy.CustomerAPI.controllertest;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.io.IOException;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public abstract class AbstractTest {

    protected MockMvc mvc;

    @Autowired
    protected ObjectMapper objectMapper;

    @Autowired
    WebApplicationContext webApplicationContext;

    protected void setUp() {
       mvc = MockMvcBuilders.standaloneSetup(new CustomerController()).build();
    }

    protected String mapToJson(Object obj) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(obj);
    }

    protected <T> T mapFromJson(String json, Class<T> clazz)
            throws JsonParseException, JsonMappingException, IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(json, clazz);
    }
}
```

To test the REST controller, we need to create an abstract class that we can use to create the web application context (to keep this straight, it's a configuration class for web applications. You can read more [here](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/WebApplicationContext.html)).

In this class, we have:

1. **MockMvc** is a class part of the Spring MVC framework. We'll need this to test the REST controller.
    
2. The **setup method** where do a standalone setup because we only want to consider the CustomerController for our application.
    
3. **MapToJson** is a method to convert a Java object into JSON.
    
4. **MapFromJson** is a method to convert from JSON to Java objects.
    

### Testing the Rest Controller

The Unit Test for the REST Controller would look like this:

```java
package com.techwithmaddy.CustomerAPI.Controller;

import com.techwithmaddy.CustomerAPI.controller.CustomerController;
import com.techwithmaddy.CustomerAPI.controllertest.AbstractTest;
import com.techwithmaddy.CustomerAPI.exception.CustomerNotFoundException;
import com.techwithmaddy.CustomerAPI.model.Customer;
import com.techwithmaddy.CustomerAPI.service.CustomerService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Optional;

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(CustomerController.class)
public class CustomerControllerTest extends AbstractTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private CustomerService customerService;

    @Test
    public void shouldSaveCustomer() throws Exception {
        Customer customer = new Customer();
        customer.setFirstName("firstName");
        customer.setLastName("lastName");
        customer.setEmail("email@test.com");
        customer.setPhoneNumber("0123456789");

        mockMvc.perform(post("/customer/save")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(customer)))
                .andExpect(status().isOk());

    }

    @Test
    public void shouldGetCustomerByEmail() throws Exception {
        Customer customer = new Customer();
        String email = "steve@austin.com";
        Optional<Customer> customerOptional = Optional.of(customer);

        when(customerService.getCustomerByEmail(email)).thenReturn(customerOptional);

        mockMvc.perform(get(String.format("/customer/retrieve"))
                .contentType(MediaType.APPLICATION_JSON)
                .queryParam("email", email))
                .andExpect(status().isOk());

    }

    @Test
    public void shouldThrowExceptionIfEmailNotFound() throws Exception {
        String email = "test@email.com";

        doThrow(new CustomerNotFoundException()).when(customerService).getCustomerByEmail(email);

        mockMvc.perform(get(String.format("/customer/retrieve"))
                .contentType(MediaType.APPLICATION_JSON)
                .queryParam("email", email))
                .andExpect(status().isNotFound());

    }


}
```

This class extends the AbstractTest class that we created before.

* **@ RunWith(SpringRunner.class)** is an annotation that gives us Spring testing functionality.
    
* **@ WebMvcTest** is an annotation used for Spring MVC tests **only**.
    

We inject MockMvc and mock CustomerService as it's a dependency.

* Each test is annotated with the @Test annotation to say that we want a specific piece of code to run as a test case.
    

The Controller class is where we handle all the incoming HTTP requests.

To test the REST Controller, we create a mock of the incoming request.

Let's look at the `shouldSaveCustomer()` test.

```java
    @Test
    public void shouldSaveCustomer() throws Exception {
        Customer customer = new Customer();
        customer.setFirstName("firstName");
        customer.setLastName("lastName");
        customer.setEmail("email@test.com");
        customer.setPhoneNumber("0123456789");

        mockMvc.perform(post("/customer/save")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(customer)))
                .andExpect(status().isOk());

    }
```

The mockMvc.perform works like this as per the [documentation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/MockMvc.html) :

Perform a request and return a type that allows chaining further actions, such as asserting expectations, on the result.

* To save a customer, we use the POST request.
    
* A POST request creates new data, and in this case, we are mocking the request of saving a new customer into the database.
    
* To save a customer into the database, we use the `/customer/save` endpoint.
    
* The body of the content type is of type JSON.
    
* We're converting the Java customer object to JSON.
    
* We expect a 200 status code (= OK).
    

shouldGetByEmail() test follows a similar logic:

```java
    @Test
    public void shouldGetCustomerByEmail() throws Exception {
        Customer customer = new Customer();
        String email = "steve@austin.com";
        Optional<Customer> customerOptional = Optional.of(customer);

        when(customerService.getCustomerByEmail(email)).thenReturn(customerOptional);

        mockMvc.perform(get(String.format("/customer/retrieve"))
                .contentType(MediaType.APPLICATION_JSON)
                .queryParam("email", email))
                .andExpect(status().isOk());

    }
```

* We want to retrieve a customer using their email.
    
* We use the `when` clause to retrieve the customer by email that will return a customer with that email.
    
* The mock request is a GET request which uses the `/customer/retrieve` endpoint.
    
* The body of the content type is of type JSON.
    
* We're adding the email as the query parameter.
    
* We expect a 200 status code (= OK).
    

Similar logic for the `shouldThrowExceptionIfEmailNotFound()` test.

```java
    @Test
    public void shouldThrowExceptionIfEmailNotFound() throws Exception {
        String email = "test@email.com";

        doThrow(new CustomerNotFoundException()).when(customerService).getCustomerByEmail(email);

        mockMvc.perform(get(String.format("/customer/retrieve"))
                .contentType(MediaType.APPLICATION_JSON)
                .queryParam("email", email))
                .andExpect(status().isNotFound());

    }
```

* We want to test that if the email doesn't exist, we throw a CustomerNotFoundException.
    
* We use the `doThrow` clause that will throw the Exception when we call the getCustomerByEmail from the service class with an email that doesn't exist.
    
* The mock request is a GET request.
    
* The body of the content type is of type JSON.
    
* We're adding the email as the query parameter.
    
* We expect a 404 status code (Not Found).
    

### Testing the Service Layer

For the testing layer, we can write integration tests to ensure that all components work as expected. The service layer communicates with the repository layer.

```java
package com.techwithmaddy.CustomerAPI.service;

import com.techwithmaddy.CustomerAPI.model.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;

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

    @Autowired
    private CustomerService customerService;

    @Test
    public void shouldSaveCustomerSuccessfully() {
        Customer customer = new Customer();
        customer.setFirstName("Richard");
        customer.setLastName("Branson");
        customer.setEmail("richard@branson.com");
        customer.setPhoneNumber("0112233445566");

        Customer savedCustomer = customerService.saveCustomer(customer);

        assertThat(savedCustomer).isNotNull();

    }

    @Test
    public void shouldGetCustomerByEmail() {
        Customer customer = new Customer();
        customer.setFirstName("Steve");
        customer.setLastName("Austin");
        customer.setEmail("steve@austin.com");
        customer.setPhoneNumber("01223344556");

        Optional<Customer> retrievedCustomer = customerService.getCustomerByEmail(customer.getEmail());

        assertEquals(retrievedCustomer.get().getFirstName(), customer.getFirstName());
        assertEquals(retrievedCustomer.get().getLastName(), customer.getLastName());
        assertEquals(retrievedCustomer.get().getEmail(), customer.getEmail());
        assertEquals(retrievedCustomer.get().getPhoneNumber(), customer.getPhoneNumber());

    }
}
```

Let's look at the test shouldSaveCustomerSuccessfully()

```java
    @Test
    public void shouldSaveCustomerSuccessfully() {
        Customer customer = new Customer();
        customer.setFirstName("Richard");
        customer.setLastName("Branson");
        customer.setEmail("richard@branson.com");
        customer.setPhoneNumber("0112233445566");

        Customer savedCustomer = customerService.saveCustomer(customer);

        assertThat(savedCustomer).isNotNull();

    }
```

In short, we want to test that this customer gets successfully saved into the database. If you run this test, Richard Branson should appear in the database table.

![richard_saved.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1638811399478/2pwzRvFcp.png align="center")

shouldGetCustomerByEmail test follows a similar logic:

```java
     @Test
    public void shouldGetCustomerByEmail() {
        Customer customer = new Customer();
        customer.setFirstName("Steve");
        customer.setLastName("Austin");
        customer.setEmail("steve@austin.com");
        customer.setPhoneNumber("01223344556");

        Optional<Customer> retrievedCustomer = customerService.getCustomerByEmail(customer.getEmail());

        assertEquals(retrievedCustomer.get().getFirstName(), customer.getFirstName());
        assertEquals(retrievedCustomer.get().getLastName(), customer.getLastName());
        assertEquals(retrievedCustomer.get().getEmail(), customer.getEmail());
        assertEquals(retrievedCustomer.get().getPhoneNumber(), customer.getPhoneNumber());

    }
```

* We want to test that the data is the same as the data in the database. If any of these fields is incorrect, the test will not pass.
    

![steve_test_passed.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1638811459626/dDteGRWiA.png align="center")

### Testing the Repository Layer

```java
package com.techwithmaddy.CustomerAPI.repository;

import com.techwithmaddy.CustomerAPI.model.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;

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

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class CustomerRepositoryTest {

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void shouldFindCustomerByEmail() {
        Customer customer = customerRepository.findCustomerByEmail("richard@branson.com");
        assertThat(customerRepository.findCustomerByEmail(customer.getEmail())).isEqualTo(customer);
    }
}
```

* This class is also annotated with the `@ DataJpaTest`, an annotation used specifically to test JPA components.
    
* Then, we check that the email has an existing customer, which it does. If the email doesn't exist, the test would fail.
    

**ADDITIONAL REFERENCES:**

* [Spring Boot - Testing](https://docs.spring.io/spring-boot/docs/1.5.7.RELEASE/reference/html/boot-features-testing.html)
    
* [DEV - Testing Spring Boot Applications](https://dev.to/sivalabs/testing-springboot-applications-4i5p)
    
* [Tutorials Point - Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_rest_controller_unit_test.htm)
    
* [freeCodeCamp - Unit Testing Services Endpoints and Repositories in Spring Boot](https://www.freecodecamp.org/news/unit-testing-services-endpoints-and-repositories-in-spring-boot-4b7d9dc2b772/)
    
* [InfoWorld - Junit 5 Tutorial](https://www.infoworld.com/article/3543268/junit-5-tutorial-part-2-unit-testing-spring-mvc-with-junit-5.html)
    
* [Source Code Examples - Spring Boot And Mockito Example](https://www.sourcecodeexamples.net/2021/08/spring-boot-junit-and-mockito-example.html)
    

You can find the complete Github repository [here](https://github.com/MaddyGre/CustomerAPI).

I hope you've found this helpful. Let me know your feedback in the comments.

Until next time! 👋🏾
