Skip to content

Repository files navigation

Spring Boot Testing

Testing library for Spring Boot projects.

Setup

Add this library to the classpath by adding the following maven dependency. Versions can be found here

<dependency>
<groupId>it.aboutbits</groupId>
<artifactId>spring-boot-testing</artifactId>
<version>x.x.x</version>
</dependency>

Usage

Validation

The validation tester allows us to quickly test simple validation constraints. Most commonly we use bean validation for this.

Configuration

To use the validation tester in your project you need to extend both the BaseValidationAssert.java and the BaseRuleBuilder.java.

publicclassValidationAssertextendsBaseValidationAssert<BaseRuleBuilder<?>> {
protectedValidationAssert() {
super(newRuleBuilder());
}
publicstaticValidationAssertassertThatValidation() {
returnnewValidationAssert();
}
publicstaticfinalclassRuleBuilderextendsBaseRuleBuilder<RuleBuilder> {
}
}

By default, the validation tester will assume that all properties of type Record are substructures. Therefore, using the @Valid annotation is required to make sure that validation for those records is triggered. You can add a class to a whitelist to disable this behavior:

publicclassValidationConfig {
publicstaticvoidconfigure() {
ValidationAssert.registerNonBeanType(NotValidated.class);
}
}
publicclassValidationAssertextendsBaseValidationAssert<BaseRuleBuilder<?>> {
static {
ValidationConfig.configure();
}
// ...
}

Usage

Each property is required to have at least one rule defined. You can add multiple rules for the same property as needed to combine more complex rulesets. The tester will fail if not all properties have rules. In case you have properties without any restrictions, use the notValidated rule.

The validation tester works by taking in a valid parameter. It will then mutate the parameter internally and test each property with an invalid value. Then a check is done if a validation violation is raised as expected.

In any case, the call to isCompliant is required at the end and then triggers the actual assertion.

You can use plain bean validation to verify a Record:

importjakarta.validation.constraints.Future;
importjakarta.validation.constraints.NotNull;
importjakarta.validation.constraints.Past;
importorg.springframework.lang.Nullable;
publicrecordSomeParameter(
@NotBlankStringname,
@Min(18)
intage,
@NotNull@PastLocalDatebirthDay,
@NullableStringsomething,
StringnotValidatedAtAll
) {
}
@TestvoidtestValidation() {
varvalidParameter = newSomeParameter("Sepp", 32);
assertThatValidation().of(validParameter)
.usingBeanValidation()
.notBlank("name")
.min("age", 18)
.notNull("birthDay")
.past("birthDay")
.nullable("something")
.notValidated("notValidatedAtAll")
.isCompliant();
}

Alternatively, you can use a method call to a service function to verify the validation. This is the preferred way as it makes sure that the bean validation is both triggered and also valid.

importorg.springframework.beans.factory.annotation.Autowired;
publicrecordSomeParameter(
@NotBlankStringname,
@Min(18)
intage
) {
}
@AutowiredprivateMyServicemyService;
@TestvoidtestValidation() {
varvalidParameter = newSomeParameter("Sepp", 32);
assertThatValidation().of(validParameter)
.calling(myService::create)
.notBlank("name")
.min("age", 18)
.isCompliant();
}
@TestvoidtestValidationWithIdParameter() {
varvalidParameter = newSomeParameter("Sepp", 32);
assertThatValidation().of(validParameter)
.calling(myService::update, newUser.ID(3L))
.notBlank("name")
.min("age", 18)
.isCompliant();
}

Adding Custom Validation Rules

You can add new rules by creating a new interface:

publicinterfaceMyShinyNewRule<VextendsBaseRuleBuilder<?>> extendsValidationRulesData {
defaultVshiny(@NonNullStringproperty) {
this.addRule(newRule(property, InertValueSource.class, newObject[0]));
return (BaseRuleBuilder) this;
}
}

To use the newly created rule, we can simply have our RuleBuilder implement the interface:

publicclassValidationAssertextendsBaseValidationAssert<BaseRuleBuilder<?>> {
// ...publicstaticfinalclassRuleBuilderextendsBaseRuleBuilder<RuleBuilder> implementsMyShinyNewRule<RuleBuilder> {
}
}

The Rule requires the property name, a value-source and an array of optional parameters. For example min(property, minValue) takes in the additional parameter for the value. Note that the value-source must return invalid values. This is required because the tool is actively trying to violate the rules to check if an error is raised.

Adding Custom Value Sources

You can add custom values sources by implementing the ValueSource interface. While the interface can not enforce the static function registerType, it is best practice to implement it in a way that keeps this extensible. This way we can use the same logical value-source for multiple property types.

Here is an example:

publicclassEmptyValueSourceimplementsValueSource {
privatestaticfinalMap<Class<?>, Function<Object[], Stream<?>>> TYPE_SOURCES = newHashMap<>();
static {
TYPE_SOURCES.put(
String.class,
(Object[] args) -> Stream.of("")
);
TYPE_SOURCES.put(
Set.class,
(Object[] args) -> Stream.of(newHashSet<>())
);
TYPE_SOURCES.put(
List.class,
(Object[] args) -> Stream.of(newArrayList<>())
);
}
publicstaticvoidregisterType(Class<?> type, Function<Object[], Stream<?>> source) {
TYPE_SOURCES.put(type, source);
}
@Override@SuppressWarnings("unchecked")
public <T> Stream<T> values(Class<T> propertyClass, Object... args) {
varsourceFunction = TYPE_SOURCES.get(propertyClass);
if (sourceFunction != null) {
return (Stream<T>) sourceFunction.apply(args);
}
thrownewIllegalArgumentException("Property class not supported!");
}
}

Adding Support for Custom Types

Note: CustomType wrappers from the toolbox are currently not natively supported.

Adding custom types will require some extension to the existing value-sources. Those need to become aware of the new type in order to produce values of said type.

This can be done by extending the configuration:

publicrecordSurnameType(
Stringvalue
) {
}
publicclassValidationConfig {
publicstaticvoidconfigure() {
EmptyValueSource.registerType(
SurnameType.class,
(args) -> {
returnStream.of(newSurnameType(""));
}
);
// ...
}
}
publicclassValidationAssertextendsBaseValidationAssert<BaseRuleBuilder<?>> {
static {
ValidationConfig.configure();
}
// ...
}

Local Development

To use this library as a local development dependency, you can simply refer to the version BUILD-SNAPSHOT.

Check out this repository and run the maven goal install. This will build and install this library as version BUILD-SNAPSHOT into your local maven cache.

Note that you may have to tell your IDE to reload your main maven project each time you build the library.

Build & Publish

To build and publish the chart, visit the GitHub Actions page of the repository and trigger the workflow "Release Package" manually.

Information

About Bits is a company based in South Tyrol, Italy. You can find more information about us on our website.

Support

For support, please contact info@aboutbits.it.

Credits

License

The MIT License (MIT). Please see the license file for more information.

About

Spring Boot - Testing

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages