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.
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.
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:
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();
…
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.
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"
]
…
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:
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.
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);
}
}
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);
}
}
The custom dummy works in my unit tests. However, we need to actually use it to send the flavours in an API response.
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();
}
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.
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:
Photo by Nattawara . from Pexels
Spring Security allows us to use role-based control to restrict access to API resources. However,…
A custom annotation in Spring Boot tests is an easy and flexible way to provide…
Delegating user management to Keycloak allows us to better focus on meeting the business needs…
Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…
Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…
Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…