DEV Community

Yüksel Özdemir
Yüksel Özdemir

Posted on

TDD using Spring BOOT

This weekend, I decided to build a sample Spring-Boot application and learn development concepts, principles, and specifically to see how Test Driven Development can be achieved in Spring-Boot by doing some actual work.

This blog post follows code in this repository

spring-boot

Spring Boot projects

Hello: A Hello Application developed by implementing with TDD approach.




Why Testing?

Testing in necessary in any field, because human beings are very likely to make mistakes anytime. Therefore, we need to check and verify that our work is completed correctly.
So why testing in software is important explained below.

  1. Point out defects and errors during development phases
  2. Reliability of organization increases in customers' perspective
  3. Ensuring quality of the product.
  4. Provides effective performance of product
  5. Foresee any failures that will cost much more in the next phases.

I think, in this 5 bullet points the most important one is the fifth one. In my opinion, in a software project we must foresee any defect or failure, only then we can create a product that is time and cost effective.

Testing Types

There are many testing types used by people developing software, but I will emphasize unit testing and integration testing in this context.

Integration Testing

This type of test is applied to see whether different components of system work well together. For more information you can see this stackoverflow link.

Unit Testing

This type of test is applied by programmer to see any unit of code is working properly. For more information you can see this stackoverflow link.

Building The Application

While building application, I used spring-initializr, added some dependencies, you can also start a maven project and add dependencies to your pom.xml

Added Dependencies

  • spring-boot-starter-web: to create web layer of Spring-Boot application.
  • spring-boot-starter-test : to use features related testing comes in bundle with Spring-Boot.
  • spring-boot-starter-data-jpa: to create entities and database layer in Spring-Boot application.
  • junit-jupiter-engine: to use test functionalities of JUnit 5.
  • lombok : to save some boilerplate code.
  • com.h2database : in-memory database.

First, I added an integration test to test happy path in the whole system. I will not dive into implementation details, but fundamentally, I used same annotations to make the integration test work. While we write integration tests, we should add these two annotations below to the top of the class, as you know, Spring-Boot uses annotation-driven approach :)

@SpringBootTest(webEnvironment=RANDOM_PORT)
Enter fullscreen mode Exit fullscreen mode

In Spring-Boot, @SpringBootTest annotation searches for the main application context, and creates a similar context to that. This annotation loads all of the application context, therefore it is suitable to use when writing integration tests.

important: Since the whole application context is loaded, frequent usage of @SpringBootTest annotation causes long running test suites.

@ExtendWith(SpringExtension.class)
Enter fullscreen mode Exit fullscreen mode

This @ExtendWith(SpringExtension.class) annotation is used to enable Spring support of JUnit 5, in JUnit 4 @RunWith(SpringRunner.class) annotation is used.

 @Autowired
 private TestRestTemplate testRestTemplate;
Enter fullscreen mode Exit fullscreen mode

As Spring Documentation insists by saying, Convenient alternative of RestTemplate that is suitable for integration tests.

We use TestRestTemplate in integration tests as part of the spring-boot-starter-test. You can inject this to your test class as you see above.

While you are writing tests, you simply should/can follow this convention below.

    @Test
    @DisplayName("Tests happy path of hello POST method")
    public void testHello_Returns200() {
        // arrange
         ...
        // act
         ...
        // assert
         ...
    }
Enter fullscreen mode Exit fullscreen mode

After I wrote integration tests, to test the whole process. Then I added tests for the web layer, so called controller.

While testing the controller, as a difference from integration test, @WebMvcTest annotation is added instead of @SpringBootTest annotation. Since @SpringBootTest loads all of the application context. We do not want the test take too long, since it is a unit test. @WebMvcTest loads only the beans required to test a web controller to the application context.

@ExtendWith(SpringExtension.class)
@WebMvcTest(HelloController.class)
Enter fullscreen mode Exit fullscreen mode

You can add these two annotations above to your controller test class. @WebMvcTest annotation takes controller as parameter, if no parameter given as controller, includes all of the controllers in the context.

@Autowired
private MockMvc mockMvc;
Enter fullscreen mode Exit fullscreen mode

While we write tests for controller layer, as the documentation implies as "Main entry point for server-side Spring MVC test support." It is a good practice to use MockMvc to simulate HTTP requests.

@MockBean
private HelloService service;
Enter fullscreen mode Exit fullscreen mode

Any service, component or repository that our controller is dependent on by dependency injection is separately included, and mocked using @MockBean annotation. This annotation adds the mocked object, in spring context it is a bean, to the application context. For a detailed information see this stackoverflow link.

After I passed all the test and refactored my code, I started to write unit test for the service layer. In this layer, I did not use any spring related context or annotation, because there was no need to load the application context. I only added the annotation below to my service test class.

@ExtendWith(MockitoExtension.class)
Enter fullscreen mode Exit fullscreen mode

Since there is no need to use Spring Application context in this layer, I preferred to use the MockitoExtension.class to test lightweight.

@Mock
private HelloRepository repository;
Enter fullscreen mode Exit fullscreen mode

Since I only used MockitoExtension.class I also used @Mock to inject mocked dependent services, components, or repositories as you can see above. In case you want to use @MockBean instead of @Mock, you need to use @ExtendWith(SpringExtension.class)
For a detailed information, when to use SpringExtension and MockitoExtension see this stackoverflow link.

After testing service layer, I continued to test data layer in the project, so called repository in Spring-Boot. While testing data layer in Spring-Boot, use these annotations below at the top of your test class.

@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE, connection = EmbeddedDatabaseConnection.H2)
Enter fullscreen mode Exit fullscreen mode

Since Spring related application context should be loaded, I used @ExtendWith(SpringExtension.class) to tell JUnit 5 to use spring extension. Also @DataJpaTest will load not whole spring application context but only JPA related context. Apart from these, I used @AutoConfigureTestDatabase annotation to configure H2 in-memory database automatically for the repository test.

@Autowired
private TestEntityManager entityManager;
Enter fullscreen mode Exit fullscreen mode

In addition to these I also used TestEntityManager in repository test to test the actual persistence to the database, if we use only the repository's save method, we do not actually fully test the behavior, jpa save method only saves to cache, therefore it is healthy to use TestEntityManager.

TDD (Test-Driven-Development)

All along, I tried to apply TDD approach while writing code. That approach is:

  1. Add a test
  2. Run all tests and see if the new test fails
  3. Write the code
  4. Run tests
  5. Refactor code
  6. Repeat

To read more about TDD, see this wikipedia link.

While I was learning how to code testing in Spring Boot I harnessed some tutorials below.

You can see these useful links and more..

Happy Coding :-)

Top comments (0)