Tools

How I enhanced my project by generating custom fake data with Dummy4j

Most fake data providers offer a wide variety of default generators and the ability to add custom definitions to them. However, ease of customization is not always a top priority in their architecture. Fortunately, one of the most important features of Dummy4j is the ability to easily extend the generator and provide custom definitions.

The kind of custom data I needed to generate

Dummy4j offers many generators, e.g. ColorDummy, FinanceDummy or NameDummy, that allow us to easily create fake data with a single line of code, like in the following code snippet:

String fakeName = dummy4j.name.fullName();

In my example project, I want to get random fake data that are not included in Dummy4j – a list of cookie flavours.

The ways of customizing Dummy4j

First, we can simply add a new yaml file with custom definitions and manually resolve a key every time we need a flavour, like below:

String chocolateVariant = dummy4j.expressionResolver.resolve("#{flavours.chocolate}");
String mixedVariant = dummy4j.expressionResolver.resolve("#{flavours.mixed}");
…

Going a step further, we can create our CustomDummy that extends Dummy4j. Thanks to that we can:

  • gain more control over our fake data,
  • unit test our extensions,
  • add as many new definitions as we want without cluttering the code,
  • still keep all the default functionality.

In this article, I’m going to describe the latter approach. I’m going to extend Dummy4j, enhance it with a new FlavourDummy and generate custom data as simply as on this code snippet:

String chocolateVariant = customDummy.flavour.chocolate();
String mixedVariant = customDummy.flavour.mixed();
String spicyVariant = customDummy.flavour.spicy();
…

How I have modified my project

For this article I use the changes I’ve introduced in my jwt-spring-boot-angular-scaffolding project as an example. The project allows for an authorised user to view a list of cookies and is a simple scaffolding to demonstrate the authorisation process. I wanted to remove hard coded flavours from the CookieController and make my code more clean.

Before using Dummy4j in the project, all flavours were generated with the following naïve code:

package in.keepgrowing.jwtspringbootangularscaffolding.cookie;
…
@RestController
@RequestMapping("api/cookies")
public class CookieController {

    @GetMapping
    public List<Cookie> getCookies() {
        return Arrays.asList(new Cookie("chocolate"), new Cookie("vanilla"),
                new Cookie("cinnamon"), new Cookie("coconut"));
    }
}

After adding the Dummy4j library and providing an abstraction for a repository, the controller looks like this:

// src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/cookie/CookieController.java
…
@RestController
@RequestMapping("api/cookies")
public class CookieController {

    private final CookieJar cookieJar;

    public CookieController(CookieJar cookieJar) {
        this.cookieJar = cookieJar;
    }

    @GetMapping
    public List<Cookie> getCookies() {
        return cookieJar.getCookies();
    }
}

Below I’m going to describe how I created my CustomDummy and used it in the CookieJar implementation – the RandomCookieJar class. All files are linked to their location in the repository so you can easily find the complete code. You can see the new files I added to the project on the following screenshot:

You can find all those changes in the 15eb8da0e539d945ecd272f539dddf02aed18fd3 commit.

Adding Dummy4j and custom definitions for fake data

First of all, we have to add the dummy4j maven dependency to our pom.xml file (visit the maven repository for the latest version):

<!-- pom.xml -->
…
<dependency>
  <groupId>dev.codesoapbox</groupId>
  <artifactId>dummy4j</artifactId>
  <version>0.6.0</version>
</dependency>
…

Secondly, we need to put the flavour.yml file in the resources/dummy4j directory, where Dummy4j looks for definitions by default:

# src/main/resources/dummy4j/flavour.yml
en:
  flavour:
    chocolate: [
        "white chocolate", "dark chocolate", "chocolate"
    ]
    nutty: [
        "hazelnut", "almond", "pecan", "coconut", "nougat"
    ]
    spicy: [
        "pumpkin spice", "cinnamon", "matcha", "mint", "honey", "vanilla", "ginger", "mocha", "fudge", "maple", "caramel",
        "toffee", "peanut butter"
    ]
…

Extending Dummy4j

Our end goal is to create a CustomDummy that serves the same dummies as the original Dummy4j plus the additional FlavourDummy. In order to achieve this, we need to:

  • extend the Dummy4j class (line 5),
  • initialize the flavourDummy object in the CustomDummy constructor (line 10),
  • write a public method that returns the FlavourDummy instance (line 13).
package in.keepgrowing.jwtspringbootangularscaffolding.dummydata;

import dev.codesoapbox.dummy4j.Dummy4j;

public class CustomDummy extends Dummy4j {

    private final FlavourDummy flavourDummy;

    public CustomDummy() {
        flavourDummy = new FlavourDummy(this);
    }

    public FlavourDummy flavour() {
        return flavourDummy;
    }
}

As a result, once we initialize the CustomDummy class, we’ll be able to reach all FlavourDummy methods by calling:

customDummy.flavour.methodName();

This allows us to hide implementation details of the FlavourDummy methods and to use our new dummy just like all other dummies that the library provides by default.

Implementing the FlavourDummy methods

The FlavourDummy constructor receives the CustomDummy instance thus giving it the whole set of Dummy4j methods to use. Therefore, we can use Dummy4j’s ExpressionResolver to get a random value for the definition key. In the example below I’m only implementing a method for getting a random mixed flavour:

package in.keepgrowing.jwtspringbootangularscaffolding.dummydata;

public class FlavourDummy {

    protected static final String FLAVOUR_MIXED_KEY = "#{flavour.mixed}";

    private final CustomDummy customDummy;

    public FlavourDummy(CustomDummy customDummy) {
        this.customDummy = customDummy;
    }

    public String mixed() {
        return customDummy.expressionResolver().resolve(FLAVOUR_MIXED_KEY);
    }
}

Unit testing the solution

We need to verify if the flavours will be returned properly, as well as document the expected behaviour. For this reason I’m going to create the FlavourDummyTest class and add the following unit test:

// src/test/java/in/keepgrowing/jwtspringbootangularscaffolding/dummydata/FlavourDummyTest.java
…
@ExtendWith(MockitoExtension.class)
class FlavourDummyTest {

    @Mock
    private CustomDummy customDummy;

    @Mock
    private ExpressionResolver expressionResolver;

    private FlavourDummy dummy;

    @BeforeEach
    void setUp() {
        dummy = new FlavourDummy(customDummy);
    }

    @Test
    void shouldGenerateMixedFlavours() {
        when(customDummy.expressionResolver())
                .thenReturn(expressionResolver);
        when(expressionResolver.resolve(FlavourDummy.FLAVOUR_MIXED_KEY))
                .thenReturn("test flavour");

        String actual = dummy.mixed();

        assertEquals("test flavour", actual);
    }
}

Using the data provider across an application

The custom dummy works in my unit tests. However, we need to actually use it to send the flavours in an API response.

Creating a contract for the data provider

We want to use our custom fake data generator as a substitute for a real repository. To minimize the impact on the code when we switch to the production data source, we may want to create an interface that both the fake and production repository have to implement:

package in.keepgrowing.jwtspringbootangularscaffolding.cookie;

import java.util.List;

public interface CookieJar {

    List<Cookie> getCookies();
}

Implementing the contract in the fake repository

Next, we can implement the interface in the RandomCookieJar class and provide 10 cookies with random flavours:

package in.keepgrowing.jwtspringbootangularscaffolding.cookie;

import in.keepgrowing.jwtspringbootangularscaffolding.dummydata.CustomDummy;
…
public class RandomCookieJar implements CookieJar {

    private final CustomDummy customDummy;

    public RandomCookieJar(CustomDummy customDummy) {
        this.customDummy = customDummy;
    }

    @Override
    public List<Cookie> getCookies() {
        List<String> flavours = customDummy.listOf(10, () -> customDummy.flavour().mixed());

        return flavours.stream()
                .map(Cookie::new)
                .collect(Collectors.toList());
    }
}

We enclosed the details of generating randomly mixed flavours in the FlavourDummy class. What’s more, we hid the fake repository under the CookieJar interface. As a result, our CookieController will know nothing about how it gets the data.

Injecting the fake repository

Finally, we can inject our fake repository to the controller. For this purpose we can configure the following Bean:

package in.keepgrowing.jwtspringbootangularscaffolding.config;

import in.keepgrowing.jwtspringbootangularscaffolding.cookie.CookieJar;
import in.keepgrowing.jwtspringbootangularscaffolding.cookie.RandomCookieJar;
import in.keepgrowing.jwtspringbootangularscaffolding.dummydata.CustomDummy;
…
@Configuration
public class BeanConfiguration {

    @Bean
    public CookieJar cookieJar() {
        return new RandomCookieJar(new CustomDummy());
    }
}

In case we have to replace the fake repository with a production one, we can simply replace the returned value with a proper CookieJar implementation.

As a result, we can inject the repository to the CookieController in the way you could see in the beginning of this article:

// src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/cookie/CookieController.java
…
private final CookieJar cookieJar;

    public CookieController(CookieJar cookieJar) {
        this.cookieJar = cookieJar;
    }

    @GetMapping
    public List<Cookie> getCookies() {
        return cookieJar.getCookies();
    }
}

In the end, we can verify the results by running the application, adding a user, and logging in to see 10 random cookies:

More on generating custom fake data

Photo by Nattawara . from Pexels

little_pinecone

Recent Posts

Simplify the management of user roles in Spring Boot

Spring Security allows us to use role-based control to restrict access to API resources. However,…

3 years ago

Create a custom annotation to configure Spring Boot tests

A custom annotation in Spring Boot tests is an easy and flexible way to provide…

3 years ago

Keycloak with Spring Boot #4 – Simple guide for roles and authorities

Delegating user management to Keycloak allows us to better focus on meeting the business needs…

3 years ago

Keycloak with Spring Boot #3 – How to authorize requests in Swagger UI

Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…

3 years ago

Keycloak with Spring Boot #2 – Spring Security instead of Keycloak in tests

Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…

3 years ago

Keycloak with Spring Boot #1 – Configure Spring Security with Keycloak

Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…

3 years ago