Test-Driven Development in Spring Boot using Pyramid Approach

Photo by Simon Matzinger on Unsplash

Introduction

In this post, we will follow the test pyramid approach in order to develop a simple Spring Boot application, which has the following classes: Controller, Service, Repository, and Domain. We will focus on Unit and Integration Tests for the Controller, Service, Repository classes.

Test Pyramid

Testing Service Components

We start with the service component is the application logic is usually written in the service component.

Unit Test

We use @RunWith and @SpringBootTest annotations for the unit test class. We don’t load the controller but load the service and its dependencies. WebEnvironment.NONE mode enables that the application should not run as a web application and should not start an embedded web server. MockitoJUnitRunner.class initializes the Mock objects, which is UserRepository.

@RunWith(MockitoJUnitRunner.class) 
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class UserServiceUnitTest { ... }

@Mock annotation is used to inject the repository. We mock the data as we don’t care about the actual data.

@Mock 
private UserRepository userRepository;

@InjectMocks annotation is used to inject the service

@InjectMocks 
private UserService userService;

@Before annotation causes that method to be run before each @Test method. If we didn’t use @RunWith(MockitoJUnitRunner.class) in the class definition, we had to call MockitoAnnotations.initMocks(this) method to initialize annotated fields.

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class UserServiceUnitTest {
...
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
...
}

Here is how we test the service and verify the result.

@Test public void testRetrieveAllUsersHappyPath() { 
// Create a user
User aMockUser = new User();
... set fields here
when(userRepository.save(any(User.class))).thenReturn(aMockUser);

// Save the user
User newUser = userService.createUser(aMockUser);
// Verify the save
assertEquals("DummyName", newUser.getName());
}

Integration Test

The integration test involves the interaction between the UserService and UserRepository. We don’t want to run any of the controllers as we only want to test the service component. We just want to access the service and data access components. We use the same configurations forUserServiceIntegrationTest.

@RunWith(SpringRunner.class) 
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class UserServiceIntegrationTest {
...
}

@Autowired annotation injects the service that we want to test

@Autowired 
private UserService userService;

Now we can test the UserService straight away

@Test 
public void testCreateUserHappyPath() {
// Create User
User aMockUser = new User();
... set fields here
// Test adding the user
User newUser = userService.createUser(aMockUser);
// Verify the addition
assertNotNull(newUser);
}

Testing Controllers

Unit Test

@WebMvcTest only scans the controller that we want to test

@RunWith(SpringRunner.class)
@WebMvcTest(value = UserController.class)
public class UserControllerUnitTest {
...
}

We inject the MockMvc to perform a request and the UserService in order to stub the user service

@Autowired 
private MockMvc mockMvc;
@MockBean
private UserService userService;

We create a mock user and simulate the form submit (POST) using the MockMvc object.

@Test
public void testCreateUserHappyPath() throws Exception {

User aMockUser = new User();
...set fields here

when(userService.createUser(any(User.class)))
.thenReturn(aMockUser);

// simulate the form submit (POST)
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(aMockUser))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andReturn();

}

Integration Test

Since we want to simulate the real environment, we use WebEnvironment.RANDOM_PORT to create a web application context (which usually triggers listening on a random port).

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {
...
}

We inject the controller

@Autowired 
UserController userController;

We create a mock user, post the User to the controller and then assert that the outcome is as expected

@Test
public void testCreateUserHappyPath() {
User aMockUser = new User();
..set fields here

// POST the User bean to the controller; check the outcome
ResponseEntity<User> newUser = userController.createUser(aMockUser);

// Assert that the outcome is as expected
assertThat(newUser.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
}

Testing Repository

Integration Test

An integration test is enough for the repository test. First, we add the following annotations to the class.

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

We inject TestEntityManager and UserRepository

@Autowired 
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;

We set up data scenario, save test data with TestEntityManager object, find an inserted record using UserRepository object, and then assert that the outcome is as expected

@Test
public void testFindBynameHappyPath() {
// setup data scenario
User aMockUser = new User();
.. set fields here

// save test data
entityManager.persist(aMockUser);

// Find an inserted record
User foundUser = userRepository.findByname("DummyName");

assertThat(foundUser.getBirthDate().toString(),
is(equalTo("2000-04-01")));
}

Create Test Suites

We can also create a test suite to run all the desired tests per future. Here is an example of all integration tests for “Create User Feature Test Suit”. Test Suite setup (annotations) is sufficient

@RunWith(Suite.class)
@Suite.SuiteClasses({UserServiceIntegrationTest.class,UserRepositoryIntegrationTest.class,UserControllerIntegrationTest.class })
public class CreateUserFeatureTestSuite {
// intentionally empty - Test Suite setup (annotations) is sufficient
}

Summary

In this post, we learned how to create Unit and Integration Tests for the Controller, Service, Repository classes for a simple Spring Boot application. You can find the source code on GitHub.

Originally published at https://github.com.

Software Engineer, Oracle Certified Java Programmer, http://suleymanyildirim.org/

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Generating a New Rails App Without Ruby Installed

images/aside-icons/info.png

DerbyJS? Never Heard Of it.

Alice in Wonderland Illustration: Alice finds a tiny door behind the curtain

Reliable Event Processing in Azure Functions

Previewing TradeTapp 15 updates

How to read file with Apache Beam

DevOps reading list: Top 30 best DevOps books you should read in 2018

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Suleyman Yildirim

Suleyman Yildirim

Software Engineer, Oracle Certified Java Programmer, http://suleymanyildirim.org/

More from Medium

Visitor Design Pattern in Java

How to implement Visitor Design Pattern using Java?

Building Microservices Application Using Spring Boot