Spring Boot REST with JPA by Example
Tags: jpa, springboot, rest, tdd
Published: 2020-05-24 15:24:52

## Project that we are building
We are going to build a todo list API, where we will have a Project that can have multiple topics. Each of the topics will have individual tasks.

A project could have one or more topics, and a topic will have one or more tasks.

In this post, we will integrate the existing application with Spring JPA. We will then write some integration tests to make sure our application works the way we want it. We will do it in the following sequences:

* Modify our `OpenAPI3` specification to accept POST request in `/projects` endpoint to save projects in the database.
* Regenerate the code using the new specifications.
* Add SpringBoot integration-test to make sure our implementation works the way we want it.
* Add a POST implementation to save the `Project` in the database.

The source code for this post will be based on my previous post about [Spring Boot Rest with OpenAPI 3](https://dev.to/alfonzjanfrithz/spring-boot-rest-with-openapi-3-4gm1-temp-slug-7302243?preview=6d27624b1aa056a9864bdc0f50b5f39617f0c4013bd549ce9ff9c66d89406ce9982584d1ed81aba948a7a9b6846c99dc1b775b1f7d42d5808d5685fa). You can follow along with the previous post before follow along with this post, or you can just check out the code in [this](https://github.com/alfonzjanfrithz/todoboard-api/commits/v0.0.1-sboot-with-openapi-3) repo to get started at the same place with me.

If you are in a rush and like to see the ending instead, have a look at [this](https://github.com/alfonzjanfrithz/todoboard-api/commits/v0.0.1-sboot-with-jpa) repo.

If you want to know a little bit more about Spring Data JPA, here is an awesome post about [The Persistent Layer with Spring Data JPA](https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa) for your reference.

## Introduction
As this post will guide through the implementation, if you want to directly see how the JPA implemented, instead of following along, you may scroll straight to the section of "JPA Implementation".

## Write the Test
### Objective
We will test the following scenarios:
* **Given** few projects created, **when** user GET `/projects` **then** return all available projects
* **Given** a project created with id `1`, **when** user GET `/projects/1` **then** return only project with `id` 1
* **Given** a project created with name `project-1`, **when** user GET `/projects?name=project-1` **then** return only project with `name=project-1`

### Change the Test Mode to Spring Boot Test

`@WebMvcTest` is not relevant anymore in our here, because we want to perform the test up to the repository level. According to the [official javadoc](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html), it will `@WebMvcTest` will disable full auto-configuration and instead apply only configuration relevant to MVC tests. This mode will only relevant if only we will mock the repository & service and focus only on the testing up to the Controller.

`@SpringBootTest` is more relevant for our case as we will use full auto-configuration to test up to database level.

`@AutoConfigureMockMvc` will be required so that we could use `MockMvc` object when we use `@SpringBootTest`. It was not required as `@WebMvcTest` will give us that object for granted.

Here how our class going to look like now, you should be able to run this with the same result. 

```java
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("ProjectController Integration Test") // junit 5 display name. This will give us a little more flexibility to name our test
@Log4j2 // this come from lombok dependency so we could log things out
class ProjectControllerIT {
    @Autowired
    private MockMvc mockMvc;

    @Test
    @DisplayName("Should Return OK Response when request sent to /projects endpoint")
    public void shouldReturnOkOnProjectsEndpoint() throws Exception {
        mockMvc.perform(get("/projects"))
                .andExpect(status().isOk());
    }
}
```

### Creating a helper test method to create a project

These helper method helps us to create a project from the endpoint. The snippet as follows.

```java
class ProjectControllerIT {
  // previous snippet...

  private ProjectResponse createOneProject() {
    return createProject("project", 1).get(0);
  }

  private List<ProjectResponse> createProject(String prefix, int numberOfProject) {
    List<ProjectResponse> listOfResponse = IntStream.range(0, numberOfProject)
      .mapToObj(num -> createOneProjectWithName(format("%s-%d", prefix, num + 1)))
      .collect(Collectors.toList());

    return listOfResponse;
  }

  private ProjectResponse createOneProjectWithName(String name) {
    try {
      MvcResult result = mockMvc.perform(
        post("/projects")
          .contentType(APPLICATION_JSON)
          .content(project(name))) // we want to create the payload using this method.
        .andExpect(status().isCreated())
        .andReturn();

      String contentResponse = result.getResponse().getContentAsString();
      ProjectResponse project = ProjectModelMapper.jsonToProject(contentResponse);

      return project;
    } catch (Exception e) {
      throw new IllegalArgumentException("Invalid Request to Create Project with name" + name, e);
    }
  }
}
```

Notice that because we want to track what we have already created when we perform the POST, the following mapper will convert the content response to the `ProjectResponse` object.

```java
// we will use this class later to also map Project from database object ot the ProjectResponse
public class ProjectModelMapper {
  private static ObjectMapper mapper = new ObjectMapper();

  public static ProjectResponse jsonToProject(String json) throws JsonProcessingException {
    return mapper.readValue(json, ProjectResponse.class);
  }
}
```

Another helper method we want to have in our testing is the method to create the payload that we want to send to the application so the code will be a little bit more readable.

```java
public class ProjectRequestBuilder {
  private static ObjectMapper mapper = new ObjectMapper();

  public static String project(String name) {
    ProjectRequest pr = new ProjectRequest();
    pr.setName(name);

    return toJson(pr);
  }

  private static String toJson(Object obj) {
    try {
        return mapper.writeValueAsString(obj);
    } catch (JsonProcessingException e) {
        throw new IllegalArgumentException("Failed to parse JSON object", e);
    }
  }
}
```

### Writing test code
We have everything we need now from the testing perspective, let's write the test code.

```java
class ProjectControllerIT {
  // other test written before

  @Test
  @DisplayName("Should return all available projects when request sent to /projects endpoint")
  public void shouldReturnAllRecordsOnProjectsEndpoint() throws Exception {
    createProject("project", 2);

    mockMvc.perform(
      get("/projects"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(2)));

    createOneProject();

    mockMvc.perform(
      get("/projects"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(3)));
  }

  // other helper methods we wrote before
}    
```

* In this test, we will call `createProject` to create a `Project`. Behind the scene, this method which will effectively fire up a POST request to `/projects`. It will form the JSON payload by transforming and object into JSON representation.
* We created two projects in the first call, so when we call the `/projects ` endpoint. we should get two projects in return. When we added another one, it will return us three results.

If we run this, we will get 501 in return because we haven't implemented anything for POST to `/projects`

#### The power of MockMVC
`MockMvc` helps us a lot writing a test, you can imagine this as your [Postman](https://www.postman.com/) that helps you fire specific request. It could also act as your eyes to validate what you want to see in a project.

The way it structured is as follows:
* **Perform an action** The action can be a GET/POST or any other HTTP method. If you have anything in the request that you wanted to include, e.g headers or body, you can do so in the action part.
* **Assert the response** `.andExpect` methods allows you to perform quite a number of assertions of the responses. You can pretty much validate any part of the response.
* **Return the result** You can also return the result for later use. For example, the result of the previous call will be used for the future call.  

### JPA Implementation

#### Dependencies
We need `spring-boot-starter-data-jpa` to helps us integrate with JPA, and we also want to use `h2` database to write the implementation using in-memory database. Lets add the following dependency to the `pom.xml`

```xml
<!--...-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>
<!--...-->
```

with those packages included in our dependencies, we need to put the configuration in our `resource/application.yaml` as follows:

```yaml
spring:
  datasource:
    url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
    username: super
    password: secret
```

This tells spring how to connect tot he database.

#### Entity class
As we want to store a project in a database, we need an entity class representation of it. To mark a class as an entity, you may use `@Entity` annotation.

The JPA default table name is the name of the class (minus the package) with the first letter capitalized. Each attribute of the class will be stored in a column in the table. `@Table` annotation allows us to specify a table name. The others annotation is [lombok](https://projectlombok.org/features/all) annotations to keep our code concise. `@Id` is used to mark the field as a primary key, and we can also specify if it is coming from auto-generated value using `@GeneratedValue`.

Here is our entity class

```java
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Table(name = "project")
public class Project {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  private Long id;
  private String name;
}
```

#### Repository to perform database operations
JPA will make things a little handy for us, what we need now is to create an interface that extends `JPARepository`. The first generics indicate the object that will be used and the second is the type of the key.

```java
public interface ProjectRepository extends JpaRepository<Project, Long> { }
```

This alone, has already give you a methods to perform basics operation such as `findAll`, `save`, `delete`. We will write a bit more modification later in this post.

Next, is either we call directly from the `controller` layer, or we wrap it in the `service` layer. I would prefer to put a service layer, for us to add a business logic at some point.

```java
@Service
public class ProjectService {
  @Autowired
  private ProjectRepository projectRepository;

  public Project createProject(Project project) {
    return projectRepository.save(project);
  }

  public List<Project> retrieveAllProjects() {
    return projectRepository.findAll();
  }
}
```

Notice that we actually never done any implementation to save an object, but `JPARepository` keep us away from creating a lot of bolierplate code e.g prepare connection, build query, extract result set, and so on, and so forth.

#### Query methods offers by JPARepository
Later on in this post, we will need to query a project by a project name. That would means we need to find the project by name. However, if we look at the `JPARepository`, there is no method to find anything by name.

To add that, we can simply add a **query method** in the `ProjectRepository` interface as the following example:

```java
public interface ProjectRepository extends JpaRepository<Project, Long> {
    List<Project> findAllByName(String name);
}
```

For more details of what can the query method does, refer to [official documentation](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods).

Thats all, we are ready to call the service from the controller and run back our test.

#### Controller implementation
In this section, we are going to implement the project creation, to save the project into the database. We will also enhance our implementation of `searchProject` so it will actually returns us the actual result from the database.

At this point, we have two `Project` object representation, one is facing the API `ProjectRequest` and `ProjectResponse`, another one is `Project` entity that facing the database. Therefore, when the object returned from the `Repository` or `Service` we need a mapper to map the entity object become the response. We will write that in this section too.

```java
@Controller
public class ProjectController implements ProjectsApi {
  @Autowired
  private ProjectService service;

  @Override
  public ResponseEntity<List<ProjectResponse>> searchProjects(@Valid String name) {
    return ResponseEntity.ok(ProjectModelMapper.toApi(service.retrieveAllProjects()));
  }

  @Override
  public ResponseEntity<ProjectResponse> createProject(@Valid ProjectRequest projectRequest) {
    Project project = service.createProject(ProjectModelMapper.toEntity(projectRequest));
    return ResponseEntity.status(CREATED).body(toApi(project));
  }
}
```

```java
public class ProjectModelMapper {
  private static ObjectMapper mapper = new ObjectMapper();

  public static Project toEntity(ProjectRequest projectRequest) {
    return Project.builder()
      .name(projectRequest.getName())
      .build();
  }

  public static ProjectResponse toApi(Project project) {
    ProjectResponse projectResponse = new ProjectResponse();
    projectResponse.setId(project.getId());
    projectResponse.setName(project.getName());

    return projectResponse;
  }

  public static List<ProjectResponse> toApi(List<Project> retrieveAllProjects) {
    return retrieveAllProjects.stream()
      .map(ProjectModelMapper::toApi)
      .collect(Collectors.toList());
  }

  public static ProjectResponse jsonToProject(String json) throws JsonProcessingException {
    return mapper.readValue(json, ProjectResponse.class);
  }
}
```

We should get our test pass for now. 

#### More tests
We have written 1 out of 3 test that we said we want to do in the previous section, here is the remaining:
* **Given** a project created with id `1`, **when** user GET `/projects/1` **then** return only project with `id` 1
* **Given** a project created with name `project-1`, **when** user GET `/projects?name=project-1` **then** return only project with `name=project-1`

Lets enhance our test code. In the following test, we will try to get the project by id. Given that the project created, we should be able to retrieved the project using the id that was returned before.

```java
@Test
@DisplayName("Should be able to retrieve project by project id")
public void shouldRetrieveExistingProject() throws Exception {
  ProjectResponse project = createOneProjectWithName("project-1");

  Long projectId = project.getId();
  mockMvc.perform(
    get("/projects/" + projectId))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.name", is("project-1")))
    .andExpect(jsonPath("$.id", is(projectId.intValue())));
}
```

Obviously the test will fail, and returns us `501` errors indicating the functionality is not implemented yet. We should then enhance our controller to include the following methods.

```java
@Override
public ResponseEntity<ProjectResponse> getProjects(@Min(1L) Long projectId) {
  Project project = service.findProject(projectId);
  return ResponseEntity.ok().body(toApi(project));
}
```

as `findProject` methods havent been implemented yet, lets get that implemented in our `service` level. The `getOne` method is the method that came with `JPARepository` we do not need to modify our repository implementation at this point.

```java
public Project findProject(Long projectId) {
    return projectRepository.getOne(projectId);
}
```

And we got the new test pass now. However, we breaks the previous test. The reason why is because, the previous test was retrieving all the projects, but because of we have another test that is also adding the project into the database, the number doesn't add up correctly. The solution is pretty simple, we should reset the database state before we executing any test. To make sure our test is independent of each other.

We can add the following method into our test, this will make sure we clear up the `project` table before any test is executing.

```java
@Autowired
private JdbcTemplate jdbcTemplate;

@BeforeEach
void setUp() {
  log.info("Deleting records from table");
  JdbcTestUtils.deleteFromTables(jdbcTemplate, "project");
}
```

Now all of our tests is passed again. The last bit of the test before we wrap up with this post is the following feature:
* **Given** a project created with name `project-1`, **when** user GET `/projects?name=project-1` **then** return only project with `name=project-1`

So here is the test:

```java
@Test
@DisplayName("Should be able to search project by name")
public void shouldRetrieveExistingProjectByName() throws Exception {
  ProjectResponse projectOne = createOneProjectWithName("Project One");
  ProjectResponse projectTwo = createOneProjectWithName("Project Two");

  mockMvc.perform(
    get("/projects").param("name", "Project One"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$", hasSize(1)))
    .andExpect(jsonPath("$[0].id", is(projectOne.getId().intValue())))
    .andExpect(jsonPath("$[0].name", is(projectOne.getName())));

  mockMvc.perform(
    get("/projects").param("name", "Project Two"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$", hasSize(1)))
    .andExpect(jsonPath("$[0].id", is(projectTwo.getId().intValue())))
    .andExpect(jsonPath("$[0].name", is(projectTwo.getName())));

  mockMvc.perform(
    get("/projects").param("name", "Project X"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$", hasSize(0)));
}
```

Again, we will fail the test because we haven't get any implementation in our controller to filter the projects by name. It should complain that the number of returned project is more than what we expect. Let's fix the code to accomodate that.

We don't have `findProjectsByName` yet in our service, we may need that so our controller can use it to get some data from the repository. So lets add the new method to help us get the `Project` by name. We have `findAllByName` query methods added before.

```java
public List<Project> findProjectsByName(String name) {
  return projectRepository.findAllByName(name);
}
```

At this point, what we need to do is to enhance our controller, to perform a search by name, if there is a name specified, otherwise, we will return all projects.

```java
@Override
public ResponseEntity<List<ProjectResponse>> searchProjects(@Valid String name) {
  if (name != null) {
    return ResponseEntity.ok(ProjectModelMapper.toApi(service.findProjectsByName(name)));
  } else {
    return ResponseEntity.ok(ProjectModelMapper.toApi(service.retrieveAllProjects()));
  }
}
```

### Wrap Up

Here is what we did so far:
* Modify our `OpenAPI3` specification to accept POST request in `/projects` endpoint to save projects in the database.
* Regenerate the code using the new specifications.
* Add SpringBoot integration-test to make sure our implementation works the way we want it.
* Add a POST implementation to save the `Project` in the database.

To see the complete source code, you may checkout the repo in my github [here](https://github.com/alfonzjanfrithz/todoboard-api/commits/v0.0.1-sboot-with-jpa)