Yeaaaaaaaaaah
 
Home » News Stories » Spring Boot Tutorial Brian Matthews

News Stories

Job Search

Events

Back to News »

Spring Boot Tutorial Brian Matthews
Feature Friday Article


Share this:
digg it  | kickit | Email it | del.icio.us | reddit | liveIt
Subscribe to IrishDev News RSS 
CategoryTechnology
DateFriday, May 14, 2021
AuthorBrian Matthews

Join Brian Matthews on this Spring Boot Featured Article: Unit and Integration Testing for Microservices Using Spring Boot Test and Test Containers

Spring Boot Tutorial Brian Matthews

Featured Article: Unit and Integration Testing for Microservices Using Spring Boot Test and Test Containers

 

Introduction

Dublin Java User Group PartnersIn this article Brian Matthews will explore some tools, frameworks and techniques that developers can use to automate their unit and integration testing. These will include Spring Boot Test, JUnit 5, Mockito, AssertJ, and Testcontainers.

 

In association with the Dublin Java User Group and their partners, Brian will be joining us live online Tuesday 25th May to give answers to your questions - register here.

 

Over to you Brian!

 

The Demo Project

The demo project is a simple micro-service back-end for a to-do list application. The REST API is implemented using Spring Boot. It accepts HTTP + JSON requests from a client uses a relational database to stores to-do items assigned to users. The relational database used is MariaDB.

This demo project doesn’t have any security and little or no input validation. Let’s pretend that’s to keep things simple and rather than have me admit that it’s because I’m lazy.

Spring Boot Tutorial Brian Matthews DubJUG IrishDev 1998 1.png

The REST API exposes the end-points described in table below using Spring Boot Web MVC:Spring Boot Tutorial Brian Matthews DubJUG IrishDev 1998 2.png

 

In the remainder of this article, for the sake of clarity and breviy, I have only included snippets of code. However, the full source is available from GitLab at https://gitlab.com/bmatthews68/testcontainers-article and can be downloaded by running the following git clone command:

Spring Boot Tutorial Brian Matthews DubJUG IrishDev 1998 3.png
 

The to-do items are stored in the database table from the schema definition below:

 
Spring Boot Tutorial Brian Matthews DubJUG IrishDev 1998 4.png
 
  1. The owner and to-do item identifiers are 128-bit type 1 UUIDs encoded as URL-safe base 64 strings which are globally unique, human-readable and relatively compact.

  2. 0 → urgent; 1 → high; 2 → medium; 3 → low.

  3. 0 → not started; 1 → in progress; 2 → done.

 

The data transfer object (DTO) below is used to represent the to-do item:

 

The data access object (DAO) persists data in a relational database using JDBC using JdbcTemplate.

 

Testing

In the following sections we are going to consider approaches for unit and integration testing the REST API.

Unit testing

Unit testing focuses on testing a single unit of an overall system. However, there is no universally agreed definition of what constitutes a single unit. Is it a single process, a module or an individual class?

 

In Java applications we typically unit test at the individual class level using frameworks like JUnit, TestNG or Spock. We will use JUnit because it is included in Spring Boot’s curated bill of materials.

 

Two important considerations to bear in mind when automating unit tests are:

    • Click to read about Brian Matthews Spring Tutorial Webinar on 30th May 2021

      Not all classes are worth unit testing — Writing and maintaining tests for the getter and setter methods of simple POJOs such as TodoItemDTO.java is probably a waste of time.

    • 100% code coverage doesn’t guarantee quality —  We often have definitions of done that call for some high percentage (e.g 85%) of the code to be covered by unit tests. On the face of it that is an admirable goal.

However:

  • The Pareto Principle easily applies to the effort of seeking 100% coverage. 80% of the code is covered by the first 20% of effort, and the remaining 20% requires 80% of the effort.

  • Even if we get to 100%, we’re still not guaranteed that we’ve tested all possible combinations of execution paths through the system.

  • Naively trying to game the coverage number by writing low value unit tests for simple accessors methods won’t guarantee that we have a stable system in production either.

 

Let’s look at some approaches we can use to unit test the JDBCTodoItemDAO.java class.

 

 

Naive approach

The naive approach would be to configure our unit tests to use a shared database server in a test lab somewhere on our network. However, this approach has the following problems:

 

  • Interference — Care has to be taken to make sure build agents and test runs do not interfere with each other especially if they are operating on shared seed data or purging data at the end of a test case.

  • Accumulated state — Tests have to be written to ensure that they clean up after themselves. Otherwise, we will end up with data accumulating in the database wasting space, impacting performance, and potentially interfering with future test runs.

  • Loss of control — In many organisations, even simple schema changes will require approval from the DBAs and may also have to be executed by the DBA on their schedule.

  • Coordination overhead — Using a shared resource is going to require an additional coordination overhead when there are multiple teams involved.

  • Accessibility — It’s not always possible for team members to access shared resources when working remotely or travelling.

 

Mocking

One approach to break the dependency on a shared database is to simulate @JdbcTemplate using a framework such as Mockito, JMock or EasyMock. We will use Mockito because like, JUnit, it is included in Spring Boot’s curated bill of materials.

 

Mocking involves simulating the behaviour of a real object with a substitute, called a mock. Mocking frameworks allow us to define rules called expectations that define how the mock object should respond to method calls depending on the values of arguments. We can even allow the substitute to call the real method or provide an alternative implementation if some complex processing is required.

 

In the example below, the mock JdbcTemplate intercepts all calls and allows the developer to provide alternate implementations or rely on the default do nothing behaviour for JdbcTemplate methods. The example below is not using any Spring Boot Test capabilities and only relies on JUnit 5, AssertJ and Mockito.

 

  1. The use of MockitoExtension will cass all attributes annotated with @Mock to be initialised with mock implementations.

  2. Mocks the @JdbcTemplate object.

  3. Mocks the PreparedStatement interface.

  4. Initialise a JDBCTodoItemDAO with a mocked @JdbcTemplate.

  5. Create an expectation that will simulate the behaviour of invoking the PreparedStatementSetter that binds the update parameters to the PreparedStatement.

 

This all a bit tedious to write and painful to maintain on an active codebase. It doesn’t really suit mocking methods that invoke callbacks and becomes totally impractical if you are using persistence frameworks, like Spring Data or Hibernate ORM, that dynamically generate the SQL for you.

 

Use an embedded database

Instead of mocking the database we can use an embedded database. An embedded database is a lightweight database runs inside the application process rather than as a standalone server.

 

Spring Boot will automatically configure an embedded database if 1) we do not explicitly configure one ourselves, and 2) it finds the driver for H2, HSQLDB, or Apache Derby on the classpath. We will be using H2 for no other reason than pure habit.

 

The following snippet from the pom.xml adds the H2 driver as a test dependency. We don’t need to specify the version number as that is provided by the Spring Boot bill of materials.

 

Spring Boot Test is able to manage the life cycle of this database. If Spring Boot finds a schema.sql file on the class path, then it will use that to create the database schema. In addition, if there is also a data.sql file on the classpath, it will be used to populate the database with seed data.

 

We’ve seen the schema.sql earlier in article and below is the data.sql.

The data.sql is only a test resource since it only contains seed data for our tests.

 

Spring Boot Test is capable of initialising all or some of an application’s context and allows Spring beans to be autowired into the test cases.

 

  1. The @DataJbcTest annotation tells Spring Boot Test to initialise the embedded database schema using schema.sql, load the seed data using data.sql, and create JDBC related beans such as JdbcTemplate.

  2. Spring Boot Test will inject the JdbcTemplate from the application for use in the test cases.

  3. Initialise the JDBCTodoItemDAO with the JdbcTemplate initialised by Spring Boot Test.

  4. By default, our test cases are considered transactional and will be rolled back upon completion. We can use the @Commit annotation if we want the changes to be persisted. However, its bad practice to rely on test case execution order.

 

This approach overcomes the concerns that arise with mocking. A real JdbcTemplate is being used, so we don’t have to worry about mocking the behaviour of methods that rely on callbacks. Its better suited to testing classes that use persistent frameworks that generate SQL statements dynamically.

 

The drawbacks are:

  • We would end up having to maintain two copies of the database schema. One for the embedded database and one for the targeted production environment.

  • We cannot test classes that on proprietary features of the targeted production environment.

 

Integration Testing

Integration testing is about testing some or all of the units of an overall system together. By this definition, it could be argued that the approach described earlier in "Use an embedded database" is a kind of integration test. However, I believe this is not really valid, because we usually wouldn’t use an embedded databases in finished products.

 

Instead, we are going to use a library called Testcontainers to manage the lifecycle of a MariaDB database. Testcontainers makes it easy to configure, launch and manage containers running any Docker image from JUnit tests. However, Testcontainers has applications specific modules for commonly used systems providing DSLs to make it even easier to configure and manage the applications running in the container. There is module for MariaDB.

 

DAO & database

First, we’ll integrate the DAO and MariaDB.

The easiest way to include Testcontainers in our project is to use its bill of materials to manage dependency versions as shown below:

 

 

Spring Boot Tutorial Brian Matthews DubJUG IrishDev 1998 11i.png

Then we can add test dependencies for the individual modules we need for JUnit 5 and MariaDB support:

 

In the example below, we are going to use a container running MariaDB that we will configure using a DSL to set the database name and user credentials, create the database schema and populate it with test data:

 

  1. We still use the @DataJdbcTest annotation to initialise the Spring Data JDBC framework for testing.

  2. We don’t want an embedded database to be configured by @DataJdbcTest, so we override the default behaviour using @AuthConfigureTestDatabase.

  3. The @Testcontainers annotation indicates that we want the Testcontainers to manage the lifecycle of container objects annotated with @Container in the test class.

  4. The @Container annotation marks the container objects created by the test case. It is static so there will only be one instance of MariaDB created for all test cases.

  5. We specify the image version rather than rely on the latest tag.

  6. Using the DSL methods to set the database name and user credentials.

  7. The MariaDB test container has support initialising the database using a single script via withInitScript() DSL method. However, we are using separate scripts for the schema and seed data. So we need to bind them to the directory that the MariaDB container image will scan on start-up. The files have to be renamed since they will be loaded in alphabetical order.

  8. Testcontainers assigns random port numbers for exposed service ports. So we can’t rely on hard-coded values in application.properties. So we have to create a dynamic property source.

  9. We bind the getJdbcUrl() DSL method to the spring.datasource.url application property. This allows auto-configuration for Spring Data JDBC correctly initialise the JdbcTemplate.

 

The drawback with this approach is that the overhead of starting/stopping containers will increase the execution time of our integration tests.

 

However, that is more that compensated for by the piece of mind we gain by more closely simulating the production environments in which our application will be expected to run.

 

 

API, DAO & database

The demo application is a micro-service with a RESTful API. If we want to have more comprehensive integration testing of the API and DAO layers with the database, then we can use Spring Boot Test’s MockMvc support.

 

  1. We want Spring Boot Test to run a servlet container with a randomly assigned port for accessing the RESTful API end-points.

  2. Using @Transactional annotation means that any database changes will be automatically rolled back after each test case.

  3. The @AutoConfigureMockMvc annotation will configure the MockMvc object to with the randomly assigned port for the servlet container.

  4. The MockMvc object we will use to test the RESTful API end-points.

  5. Construct the GET method to be performed injecting path variables and setting headers.

  6. Logs the outgoing request and incoming response.

  7. Verifies the status code.

  8. Using JsonPath to verify the contents of the response body.

 

Conclusion

In this article we’ve examined approaches for unit and integration testing applications using JUnit 5, Testcontainers and Spring Boot Test.

Hopefully, we can agree that Testcontainers is powerful, yet easy to use, and should be preferred over relying on embedded databases.

If that’s not enough consider that Testcontainers has DSLs for other relational and non-relational databases, Docker Compose, Elasticsearch, GCloud, Kafka, Localstack, Mockserver, Nginx, Apache Pulsar, RabbitMQ, Solr, Toxiproxy, Hashicorp Vault, and web drivers.

If the container you need is not supported directly you can still deploy it as a generic container or even create your own DSL module to share with the world.

 

If you'd like to be a guest writer on IrishDev.com, the online home of the Irish Software Developers' Network, check out our Guest Writers section

 

Author Information

Brian Matthews is an architect/engineer specialising in the development of cloud native applications for the Java and Spring eco-system.

Living in Russia, Brian regularly visits his homeland Ireland to share his knowledge with the Dublin Java User Group members.

Visit: www.linkedin.com/in/bmatthews68/ / @btmatthews68

 

 

Our subscribers are also reading....

Related: Java News, Events, Jobs

 

Get Instant Irish Tech News Updates on our Social Channels....

Join IrishDev.com at Facebook Join IrishDev.com at LinkedIn Follow IrishDevdotcom on Twitter

Got a Story – Share it with the Irish Software Community – Email us at

Back to News »
digg it  | kickit | Email it | del.icio.us | reddit | liveIt | RSS
E-mail
Low Cost, No Frills Coworking and Hotdesks
Unix Tutorials