Semir Umut Kurt
Jan 3 · 11 min read

SPRING BOOT WEB APP WITH TDD & CI PART 1

Hello folks. I want to take you a journey about Spring Boot, Hibernate, Integration Testing, Unit Testing, MVC, DTO, Mapstruct, Lombok, TDD, and CI. This journey will consist of 3 parts. Here is the outline of the first part:

  • UML Class Diagram
  • CircleCI configurations
  • Maven dependencies
  • Create models and short glances to Hibernate
  • Issue Tracking on GitHub and working with multiple branches
  • DTO Pattern
  • Create mappers with Maptstruct and short glances to Lombok
  • Spring Data JPA
  • Service Layer and short glances to Multi-Layered Architecture
  • Unit Testing, Integration Testing, MockMVC
  • Thymeleaf

1. UML CLASS DIAGRAM OF THE OBJECTS IN OUR RECIPE PROJECT

Class diagrams clearly map out the structure of a particular system by modeling its classes, attributes, operations, and relationships between objects.

Classes, Attributes, and Relations

2. CREATE NEW PROJECT

You can create your spring boot project either from here or your favorite IDE. I created the project using Intellij. Here is the 2 step I took to create the project:

Specify the project name, group, artifact and then select the dependencies

POM (Project Object Model) file of the project can be reached via https://github.com/sumut/spring-boot-recipe-tutorial/tree/webjar-mapstruct-lombok-dependency-config

3. VERSION CONTROL SYSTEM (VCS) AND FIRST COMMIT

What is Version Control?
Version control is a program that allows you to manage changes over time. You can track revisions of your project’s assets.

During this project, I will use Git. There are other VCS tools if you want to learn about them then follow the link.

I recommend you to watch this video so that you will learn how to enable version control system, commit and contribute to the projects.

After creating the project, I shared it on GitHub and then did my initial commit. The default commit branch name is master.

write meaningful commit messages

4. ISSUE TRACKING SYSTEM (ITS)

What is an Issue Tracking System (ITS)?

Issue tracking systems, commonly referred to as ITS, are software applications that provide a ticketing system to record and follow the progress of every issue identified by a computer user until the issue is resolved.

issue/bug tracking on GitHub

There are ITS with the sole aim of issue/bug tracking. JIRA is the most popular one. In the upcoming tutorials, I am planning to use JIRA.

On GitHub, issues can be assigned to individuals and they can have a description.

5. CONTINUOUS INTEGRATION

Continuous integration (CI) is the practice of automating the integration of code changes from multiple contributors into a single software project. The CI process is comprised of automatic tools that assert the new code’s correctness before integration.

As a continuous integration tool, I used CircleCI. The alternatives are Jenkins, TravisCI, TeamCity.

A continuous integration tool pulls the commit in real-time, create an executable file and then run it. If there is an error in code, contributors will be notified by email.

A pipeline in a Software Engineering team is a set of automated processes that allow Developers and DevOps professionals to reliably and efficiently compile, build and deploy their code to their production compute platforms. The continuous integration process is a part of the pipeline

6. CircleCI CONFIGURATION

Go to CircleCI web page and then set up a project as shown plow

Then go to your project and create folder named .circleci Under that folder create a file named config.yml . Here is the content of config.yml

# Java Maven CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/openjdk:8-jdk

# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/postgres:9.4
working_directory: ~/repo
environment:
# Customize the JVM maximum heap limit
MAVEN_OPTS: -Xmx3200m

steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: mvn dependency:go-offline
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}

# run tests!
- run: mvn package

then commit your changes into the branch named circle-ci-config.

after pushing commit then merge the branch with master

git checkout master
git merge circle-ci-config
git push
merging branches and then committing changes

Why I am merging branches?
I personally open a new branch per issue so that I easily can see changes between different commits. I’m also merging the newly committed push with the master branch so that I can see the whole project in one branch.

Be careful with commit messages on Git. If you type Closes #N this will closed the N’th issue. (n is a positive integer)

Till now, what we have done is here https://github.com/sumut/spring-boot-recipe-tutorial/tree/circle-ci-config

7. MULTI-LAYERED ARCHITECTURE (TIERED ARCHITECTURE)and MVC

Sometimes called tiered architecture, or n-tier architecture, a multi-layered software architecture consists of various layers, each of which corresponds to a different service or integration. Because each layer is separate, making changes to each layer is easier than having to tackle the entire architecture.

What does a layered software architecture consist of?

  • Application layer
  • Data layer

For detailed information, please see https://hub.packtpub.com/what-is-multi-layered-software-architecture/

MVC Framework

MVC is short for Model, View, and Controller. MVC is a popular way of organizing your code. The big idea behind MVC is that each section of your code has a purpose, and those purposes are different. Some of your code holds the data of your app, some of your code makes your app look nice, and some of your code controls how your app functions.

Basically we can say that MVC is an implementation of Multi-Layered Architecture.

I followed the tiered architecture concept in this project. Let’s take a look into Models of MVC.

8. CREATING MODELS

Models are basically correspondence of unique tables in the database. Not every table has a correspondent model but the reverse is true.

But wait a second… We have a different understanding of representing objects in a database. So how can we define the relations in OOP?

Good question… The answer is mappings.

Before moving on creating our models it would be good to talk about precious Hibernate and JPA.

Let’s take a look at the JPA specification first.

What is JPA?

JPA can be seen as a bridge between object-oriented domain models and relational database systems. Being a specification, JPA doesn’t perform any operation by itself. Thus, it requires implementation. So, ORM tools like Hibernate, TopLink, and iBatis implements JPA specifications for data persistence.

What is Hibernate?

Hibernate is an implementation of JPA. So, it follows the common standards provided by the JPA.

So what we should understand is that interaction with database and database operations are done using JPA and ORM framework.

Important Concepts Of Hibernate

  • There will be a session factory per database.
  • The SessionFacory is built once at start-up
  • It is a thread-safe class
  • SessionFactory will create a new Session object when requested

Session:

  • The Session object will get a physical connection to the database.
  • A session is the Java object used for any DB operations.
  • A session is not thread-safe. Hence do not share hibernate session between threads
  • A session represents a unit of work with database
  • A session should be closed once the task is completed

8. CREATE MODELS

The models in this project are Recipe, Ingredient, Category, Note, and UnitOfMeasure.

@Getter
@Setter
@Entity
public class Recipe extends BaseEntity {
private String description;
private Integer prepTime;
private Integer cookTime;
private Integer servings;
private String source;
private String url;
@Lob
private String directions;
@Lob
private Byte[] image;

@OneToOne(cascade = CascadeType.ALL)
private Note note;

@Enumerated(value = EnumType.STRING)
private Difficulty difficulty;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "recipe")
private List<Ingredient> ingredients = new ArrayList<>();

@ManyToMany
@JoinTable(name = "recipe_category", joinColumns = @JoinColumn(name = "recipe_id"), inverseJoinColumns = @JoinColumn(name = "category_id"))
private List<Category> categories = new ArrayList<>();
}

Above is the implementation of the Model object. Other model implementations can be seen here https://github.com/sumut/spring-boot-recipe-tutorial/tree/create-models

In commit message don’t forget to close the issue. After committing the changes, merge the current working branch with master.

9. CREATE DTO DATA TRANSFER OBJECT

What is the point of using DTO?

We can hide secret fields or remove redundant fields when we are transferring the data remote interfaces (e.g., web services), where each call is an expensive operation.

@Setter
@Getter
public class RecipeDto {
private Long id;
private String description;
private Integer prepTime;
private Integer cookTime;
private Integer servings;
private String source;
private String url;
private String directions;
private Byte[] image;
private List<IngredientDto> ingredients = new ArrayList<>();
private Difficulty difficulty;
private NoteDto note;
private List<CategoryDto> categories = new ArrayList<>();
}

Above is the implementation of RecipeDto other DTO objects can be found https://github.com/sumut/spring-boot-recipe-tutorial/tree/create-dtos

After implementing DTO please write meaningful commit messages, close the issue and merge the current branch to master.

10. MAPSTRUCT and Lombok

I guess you noticed that we should map the model object to DTO. Instead of writing converters manually we can use Maspstruct which reduces coding by hundreds of lines.

@Mapper
public interface RecipeMapper {
Recipe dtoToEntity(RecipeDto recipeDto);
RecipeDto entityToDto(Recipe recipe);
}

Here is all you should do to convert DTO to an entity or reverse conversion.

Other mappers can be found https://github.com/sumut/spring-boot-recipe-tutorial/tree/create-mappers

On the other side, Lombok enables us to have cleaner POJO objects. It provides setter, getter, equal and hash code, all arg constructor, no-arg constructor, required arg constructor.

11. JPA REPOSITORIES AND FREQUENTLY USED ANNOTATIONS

JpaRepository extends PagingAndSortingRepository which extends CrudRepository.

It provides us numerous database manipulation query methods and much more.

Architectural Overview of JpaRepository

data and application layer

Frequently used annotations:

  • The @Entity annotation will map this POJO into the database with all of its fields.
  • The @Id annotation marks the field as the primary key of the table.
  • The @GeneratedValue annotation practically sets the AUTO_INCREMENT option of the primary key to true. You can optionally add (strategy = GenerationType.AUTO) to achieve this.
  • The @Transactional annotation
  • The @Repository annotates classes at the persistence layer, which will act as a database repository. @Repository’s job is to catch persistence specific exceptions and rethrow them as one of Spring’s unified unchecked exceptions.
  • The @Sercvice annotates classes at the service layer. @Service indicates that it’s holding the business logic.
  • The @Controllerannotation can be applied to classes only. It’s used to mark a class as a web request handler. It’s mostly used with Spring MVC application.
public interface RecipeRepository extends JpaRepository<Recipe, Long> {}

Above shows RecipeRepository, other repositories are here https://github.com/sumut/spring-boot-recipe-tutorial/tree/jpa-repositories

12. SERVICE LAYER, UNIT TESTING, AND INTEGRATION TESTING

The service layer holds the business logic. I implemented Recipe service and tests here https://github.com/sumut/spring-boot-recipe-tutorial/tree/recipe-service-unit-test-integrationTest

Test-Driven Development

In test-driven development firstly you declare service/repository/controller method and return null. These methods like interface/abstract method the difference is that you implement them with one line by returning null as shown below:

method implementation in test-driven development

Then you go and write a test method that will fail in the beginning. After writing the test method return back to the method in the pic and implement it. After that re-run test method, it should run successfully!

TDD will make you think through your requirements or design before you write your functional code.

Unit Testing

class RecipeServiceImplTest {

RecipeServiceImpl recipeService;

@Mock
RecipeRepository recipeRepository;

@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
recipeService = new RecipeServiceImpl(recipeRepository);
}

@Test
void getRecipes() {
Recipe recipe = new Recipe();
List<Recipe> recipes = new ArrayList<>();
recipes.add(recipe);

when(recipeRepository.findAll()).thenReturn(recipes);
assertEquals(recipes.size(), 1);

List<RecipeDto> returnedRecipes = recipeService.getRecipes();

assertEquals(returnedRecipes.size(), 1);
verify(recipeRepository, times(1)).findAll();
verify(recipeRepository, never()).findById(anyLong());

}
}

Integration Testing

@ExtendWith(SpringExtension.class)
@DataJpaTest
class UnitOfMeasureRepositoryTestIT {

@Autowired
UnitOfMeasureRepository unitOfMeasureRepository;

@Test
void findByDescription() {
Optional<UnitOfMeasure> unitOfMeasureOptional = unitOfMeasureRepository.findByDescription("Teaspoon");
assertEquals("Teaspoon", unitOfMeasureOptional.get().getDescription());
}
}

SpringExtension introduced in Spring 5, is used to integrate Spring TestContext with JUnit 5 Jupiter Test. SpringExtension is used with JUnit 5 Jupiter @ExtendWith annotation as following.

Spring MockMVC to perform integration testing of Spring web MVC controllers. MockMVC class is part of the Spring MVC test framework which helps in testing the controllers explicitly starting a Servlet container.

MockMVC example

class IndexControllerTest {
@Mock
RecipeService recipeService;

@Mock
Model model;

MockMvc mockMvc;
IndexController controller;


@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
controller = new IndexController(recipeService);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}

@Test
void getIndexPage() {
//given
List<RecipeDto> recipes = new ArrayList<>();
recipes.add(new RecipeDto());

RecipeDto recipe = new RecipeDto();
recipe.setId(1L);

recipes.add(recipe);

when(recipeService.getRecipes()).thenReturn(recipes);

ArgumentCaptor<List<RecipeDto>> argumentCaptor = ArgumentCaptor.forClass(List.class);

//when
String viewName = controller.getIndexPage(model);


//then
assertEquals("index", viewName);
verify(recipeService, times(1)).getRecipes();
verify(model, times(1)).addAttribute(eq("recipes"), argumentCaptor.capture());
List<RecipeDto> setInController = argumentCaptor.getValue();
assertEquals(2, setInController.size());
}

@Test
void mockMvc() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"));
}
}

I think I mentioned all the important parts and since it has been a long tutorial so that I decided to cut it short. All other branches can be reached here https://github.com/sumut/spring-boot-recipe-tutorial

After doing all steps, when you run the application, you will get :

Thank you for reading the first part of the recipe app tutorials. Please give me feedback. I’m always open to new ideas, and your opinions are so valuable to me!

Semir Umut Kurt

Written by

Junior Software Developer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade