cft
Become a CreatorSign inGet Started

Realistic Test Data Generation for Java Apps

In this article, I'll show you how to use Vaadin's example data generator to create demo data for a simple SQL database. The article shows how to build a complete app combining Spring Boot, JPA, Project Lombok, Vaadin, and MariaDB


user

Alejandro Duarte

3 months ago | 8 min read
Follow

realistic-test-data-generation-java-apps-xoxzw

Might as well make it real.

The closer your test or demo data is to the real world the better you can test the application for UX, improve, and catch corner cases during development.

In this article, I'll show you how to use Vaadin's example data generator to create demo data for a simple SQL database. The article shows how to build a complete app combining Spring BootJPAProject LombokVaadin, and MariaDB.

You can also watch a video version of this article:

Setting Up a Project

In this article, I use a Vaadin Flow project generated using an online tool called Vaadin Start. However, you can use Vaadin's example data generator in any Java project with or without Vaadin.

Adding the Required Dependencies

To use the Vaadin example data generator, add the following dependency to the pom.xml file:

XML1

<dependency>

2

<groupId>com.vaadin</groupId>

3

<artifactId>exampledata</artifactId>

4

<version>4.0.0</version>

5

</dependency>

6

If you are following this article from scratch, add the Spring Data, MariaDB JDBC driver, and Project Lombok dependencies as well:

XML1

<dependency>

2

<groupId>org.springframework.boot</groupId>

3

<artifactId>spring-boot-starter-data-jpa</artifactId>

4

</dependency>

5

<dependency>

6

<groupId>org.mariadb.jdbc</groupId>

7

<artifactId>mariadb-java-client</artifactId>

8

<scope>runtime</scope>

9

</dependency>

10

<dependency>

11

<groupId>org.projectlombok</groupId>

12

<artifactId>lombok</artifactId>

13

<optional>true</optional>

14

</dependency>

Implementing a Simple Backend

Let's say we want to implement a view to show a list of books. We need to configure a database connection, create a Java class that represents a book (as a JPA Entity), create a class (or interface) to access the database using JPA, and implement a service class to encapsulate database technology details.

Configuring the Database Connection

With Spring Boot, you can configure the database connection in the application.properties file by adding the following:

Properties files1

spring.datasource.url=jdbc:mariadb://localhost:3306/book_demo

2

spring.datasource.username=root

3

spring.datasource.password=password

4

spring.jpa.hibernate.ddl-auto=create

The last line is needed only if you want to drop and recreate the database schema every time the application is started. There are other options that could be more convenient during development depending on the stage you are in your project.

Remember to set the database connection parameters to point to your database instance.

Implementing a JPA Entity

Implementing a JPA Entity gets easier with Project Lombok. Here's a possible implementation:

Java1

package com.example.application.backend;

2

3

import lombok.Data;

4

import lombok.EqualsAndHashCode;

5

6

import javax.persistence.*;

7

import java.time.LocalDate;

8

9

@Data

10

@EqualsAndHashCode(onlyExplicitlyIncluded = true)

11

@Entity

12

public class Book {

13

14

@EqualsAndHashCode.Include

15

@Id

16

@GeneratedValue(strategy = GenerationType.IDENTITY)

17

private Integer id;

18

19

@Lob

20

private String imageData;

21

22

private String title;

23

24

private String author;

25

26

private LocalDate publishDate;

27

28

private Integer pages;

29

30

}

This class is persistence-ready which means that JPA will be able to map instances of this class to a MariaDB database table (or any other database that provides a JDBC driver). Important here is to notice that we want the id column to be automatically generated for us if we pass a null value. Lombok's @Data annotation adds getters and setters, and @EqualsAndHashCode... I'm sure you can guess what it does. What's important is that we are indicating Lombok to use only the id property for the equals(Object) and hashCode() methods. So, two books are the same if they have the same id values regardless of whether the other properties have different values or not. Having these two methods implemented is needed for the correct functioning of JPA.

Implementing a Repository cClass

We need a way to access the database. We could use the JDBC API to connect to the MariaDB database, run SQL queries, and manually set the returned values in instances of Book. However, JPA mission is to provide this functionality and it is augmented by Spring Data. Accessing the database is so easy, that we only need to declare an interface:

Java1

package com.example.application.backend;

2

3

import org.springframework.data.jpa.repository.JpaRepository;

4

import org.springframework.stereotype.Repository;

5

6

@Repository

7

public interface BookRepository extends JpaRepository<Book, Integer> {

8

}

There's no need to implement this interface at all. Spring Data will provide objects that implement the interface when needed. If you inspect the methods available in the interface, you'll find many useful ones to create, read, update, delete Book instances. You can also add methods to the interface without implementing them and Spring Data will use a naming convention to create the implementation for you.

Implementing a Service Class

The BookRepository interface doesn't hide the fact that we are using JPA as a persistence mechanism. To improve code maintainability, we can introduce a new class that uses the repository and provides the methods that the UI needs. This class could also add any additional business logic required by the application:

Java1

package com.example.application.backend;

2

3

import org.springframework.stereotype.Service;

4

5

import java.util.List;

6

7

@Service

8

public class BookService {

9

10

private final BookRepository repository;

11

12

public BookService(BookRepository repository) {

13

this.repository = repository;

14

}

15

16

public List<Book> findAll() {

17

return repository.findAll();

18

}

19

20

}

The constructor of this service class accepts an object of type BookRepository. Since the class is also marked with @Service, Spring will create a new instance of the repository and pass it to the constructor when you, in turn, have a constructor in a different class (for example, another service, or the UI when implemented in Java) that accepts a BookService object. Spring creates all the instances for you using a pattern called Inversion of Control so you never use the new Java keyword to create these instances and give Spring the chance to pass objects via the constructors using a pattern called Dependency Injection. There are many online resources to learn more about this.

Using Vaadin's Example Data Generator

A good point to generate demo data is at application startup. To run a Java method when the application starts, we can create a Spring bean of type CommandLineRunner in any configuration class, for example, we can add the following method to the Application class:

Java1

@Bean

2

public CommandLineRunner createDemoDataIfNeeded(BookRepository repository) {

3

return args -> {

4

... logic here ...

5

};

6

}

Spring will inject the required BookRepository object before executing the method.

Configuring the Generator

Vaadin's example data generator is accessed through the ExampleDataGenerator class. Here's the code we can add inside the lambda expression of the previous code snippet:

Java1

if (repository.count() == 0) {

2

var generator = new ExampleDataGenerator<>(Book.class, LocalDateTime.now());

3

generator.setData(Book::setImageData, DataType.BOOK_IMAGE_URL);

4

generator.setData(Book::setTitle, DataType.BOOK_TITLE);

5

generator.setData(Book::setAuthor, DataType.FULL_NAME);

6

generator.setData(Book::setPublishDate, DataType.DATE_LAST_10_YEARS);

7

generator.setData(Book::setPages, new ChanceIntegerType("integer", "{min: 20, max: 1000}"));

8

9

List<Book> books = generator.create(100, new Random().nextInt());

10

}

This checks that there are no books in the database since we don't want to mess up with data if it already exists.

The generator is configured by using the setData(BiConsumer, DataType) method which accepts a method reference to a setter in the Book class and a specific data type. There are many data types available. Make sure to inspect the values in the DataType class to get an idea. You'll find for example data types for creating book titles, people's names, dates, times, cities, countries, phone numbers, addresses, food names, words, sentences, numbers, booleans, and others.

Creating and Saving the Example Data

Call the create(int, int) method to create Book instances:

Java1

List<Book> books = generator.create(100, new Random().nextInt());

The first parameter is the number of instances to create (100 books in the previous example), and the second is the seed used by the internal random generator. The method returns a list of objects that we can persist using the repository instance:

Java1

repository.saveAll(books);

Measuring Data Generation and Saving Time

It's useful to show a message in the log when the application is generating data, a process that can take time depending on the kind and amount of data to create. It's also useful to show a message when the data generation process is completed, possibly, showing the time it took. Here's a complete implementation of the createDemoDataIfNeeded(BookRepository) method that does exactly that:

Java1

@SpringBootApplication

2

@Theme(value = "demo")

3

@PWA(name = "Demo", shortName = "Demo", offlineResources = {"images/logo.png"})

4

@NpmPackage(value = "line-awesome", version = "1.3.0")

5

@Log4j2

6

public class Application extends SpringBootServletInitializer implements AppShellConfigurator {

7

8

public static void main(String[] args) {

9

SpringApplication.run(Application.class, args);

10

}

11

12

@Bean

13

public CommandLineRunner createDemoDataIfNeeded(BookRepository repository) {

14

return args -> {

15

if (repository.count() == 0) {

16

log.info("Generating demo data...");

17

var generator = new ExampleDataGenerator<>(Book.class, LocalDateTime.now());

18

generator.setData(Book::setImageData, DataType.BOOK_IMAGE_URL);

19

generator.setData(Book::setTitle, DataType.BOOK_TITLE);

20

generator.setData(Book::setAuthor, DataType.FULL_NAME);

21

generator.setData(Book::setPublishDate, DataType.DATE_LAST_10_YEARS);

22

generator.setData(Book::setPages, new ChanceIntegerType("integer", "{min: 20, max: 1000}"));

23

24

var stopWatch = new StopWatch();

25

stopWatch.start();

26

List<Book> books = generator.create(100, new Random().nextInt());

27

repository.saveAll(books);

28

stopWatch.stop();

29

log.info("Demo data generated in " + stopWatch.getTime() + "ms.");

30

}

31

};

32

}

33

34

}

This uses Apache Commons (StopWatch) for timing and Lombok (@Log4j2) for logging.

Implementing a Web View in Java

You can check that the data is indeed in the database by connecting to the MariaDB instance and running the following query:

MariaDB SQL1

select * from book;

However, to make it more interesting we can add a web view using Vaadin and explore the data in the browser:

Java1

package com.example.application.ui;

2

3

import com.example.application.backend.Book;

4

import com.example.application.backend.BookService;

5

import com.vaadin.flow.component.dialog.Dialog;

6

import com.vaadin.flow.component.grid.Grid;

7

import com.vaadin.flow.component.html.Image;

8

import com.vaadin.flow.component.orderedlayout.VerticalLayout;

9

import com.vaadin.flow.router.Route;

10

11

@Route("")

12

public class BooksView extends VerticalLayout {

13

14

public BooksView(BookService service) {

15

var grid = new Grid<Book>();

16

grid.setSizeFull();

17

grid.addComponentColumn(this::getThumbnail);

18

grid.addColumn(Book::getTitle).setHeader("Title");

19

grid.addColumn(Book::getAuthor).setHeader("Author");

20

grid.addColumn(Book::getPublishDate).setHeader("Publish date");

21

grid.addColumn(Book::getPages).setHeader("Pages");

22

23

grid.setItems(service.findAll());

24

25

add(grid);

26

setSizeFull();

27

}

28

29

private Image getThumbnail(Book book) {

30

var image = new Image(book.getImageData(), book.getTitle() + " cover");

31

image.setHeight("70px");

32

image.addClickListener(event -> showCover(book));

33

return image;

34

}

35

36

private void showCover(Book book) {

37

var image = new Image(book.getImageData(), "Cover");

38

image.setSizeFull();

39

40

var dialog = new Dialog(image);

41

dialog.setHeight("90%");

42

dialog.open();

43

}

44

}

This class uses Vaadin's API to add a view mapped to the context root via @Route(""). The constructor creates a Grid UI component and configures the columns connecting each one to a property in the Book class using the corresponding getters.

There's a special column that shows a thumbnail image that, when clicked, opens a dialog to show the book's cover as a larger image.

To start the application, run:

Plain Text1

mvn spring-boot:run

Alternatively, you can simply run the standard entry point main(String[]) method in the Application class. Once the application is compiled and started (a process that could take longer if it's the first time you are building it), you can access it in the browser at http://localhost:8080. Here's a screenshot:

Upvote


user
Created by

Alejandro Duarte

Follow

Developer Advocate at MariaDB Corporation

Software Engineer · Published Author · Developer Advocate


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles