Skip to content

spring-projects-experimental/spring-boot-testjars

Repository files navigation

spring-boot-testjars

Spring Boot + Testcontainers support for Spring Boot applications, but without the need for a Docker container.

This project is intended to add support for running external Spring Boot applications during development and testing by building on Spring Boot’s existing support. It is composed of two main features:

Note
This project is an experimental project and may make breaking changes including, but not limited to, the name of the project.

Videos

Motivation

Why not just create a Docker image of the Spring Boot application and use Testcontainers?

  • Lighter weight than spinning up a Docker image

  • When you are consuming dependencies, you don’t always have a docker image available. Sure you can create one, but why add additional overhead?

Maven / Gradle

You can add Spring Boot Testjars to your project by adding them to your Gradle or Maven build.

build.gradle
testImplementation ('org.springframework.experimental.boot:spring-boot-testjars:0.0.1')
pom.xml
<dependency>
    <groupId>org.springframework.experimental.boot</groupId>
    <artifactId>spring-boot-testjars</artifactId>
    <version>0.0.1</version>
</dependency>

Releases are published to Maven Central. For any other release type, refer to the Spring Repositories.

Starting an External Spring Boot Application

This project allows users to easily start an external Spring Boot application by creating it as a Bean. For example, the code below will start (on a arbitrary available port) and stop an external Spring Boot application as a part of the lifecycle of the Spring container:

@Bean
static CommonsExecWebServerFactoryBean messagesApiServer() {
  return CommonsExecWebServerFactoryBean.builder()
    .classpath((cp) -> cp
        .files("build/libs/messages-0.0.1-SNAPSHOT.jar")
    );
}

The CommonsExecWebServerFactoryBean creates a CommonsExecWebServer and the property CommonsExecWebServer.getPort() returns the port that the application starts on.

MavenClasspathEntry

User’s can also resolve Maven dependencies from Maven Central. The following will add spring-boot-starter-authorization-server and it’s transitive dependencies to the classpath.

@Bean
@OAuth2ClientProviderIssuerUri
static CommonsExecWebServerFactoryBean authorizationServer() {
	// @formatter:off
	return CommonsExecWebServerFactoryBean.builder()
		// ...
		.classpath((classpath) -> classpath
			// Add spring-boot-starter-authorization-server & transitive dependencies
			.entries(springBootStarter("oauth2-authorization-server"))
		);
	// @formatter:on
}

You can also use the MavenClasspathEntry constructor directly or additional helper methods to add dependencies other than Spring Boot starters to the classpath.

To use this feature, use the feature variant named maven by adding it to your Gradle or Maven build.

build.gradle
testImplementation ('org.springframework.experimental.boot:spring-boot-testjars:0.0.1') {
        capabilities {
            requireCapability("org.springframework.experimental.boot:spring-boot-testjars-maven")
        }
    }
pom.xml
<dependency>
    <groupId>org.springframework.experimental.boot</groupId>
    <artifactId>spring-boot-testjars</artifactId>
    <classifier>maven</classifier>
    <version>0.0.1</version>
</dependency>

Custom Maven Repositories

By default, only Maven Central is searched. You can customize the repositories that are searched by injecting the repositories like the example below:

List<RemoteRepository> repositories = new ArrayList<>();
repositories.add(new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build());
repositories.add(new RemoteRepository.Builder("spring-milestone", "default", "https://repo.spring.io/milestone/").build());
MavenClasspathEntry classpathEntry = new MavenClasspathEntry("org.springframework:spring-core:6.1.0-RC1", repositories);

Default Java Main

In some cases you need to provide a Java main class without any additional configuration. If that is the case, you can do it with the following:

@Bean
@OAuth2ClientProviderIssuerUri
static CommonsExecWebServerFactoryBean authorizationServer() {
	// @formatter:off
	return CommonsExecWebServerFactoryBean.builder()
		// ...
		// Add a class annotated with SpringBootApplication and a main method to the classpath and use it as the main class
		.defaultSpringBootApplicationMain();
	// @formatter:on
}

Default application.yml / application.properties

If present, CommonsExecWebServerFactoryBean will add the resources webjars/$beanName/application.yml or webjars/$beanName/application.properties exists, then it is automatically added to the classpath as application.yml and application.properties respectively.

@DynamicProperty

This is an extension to Spring Boot’s existing DynamicPropertyRegistry. It allows annotating arbitrary Spring Bean definitions and adding a property that references properties on that Bean.

For example, the following @DynamicProperty definition uses SpEL with the current Bean as the root object for the value annotation to add a property named messages.url to the URL and the arbitrary available port of the CommonsExecWebServer:

@Bean
@DynamicProperty(name = "messages.url", value = "'http://localhost:' + port")
static CommonsExecWebServerFactoryBean messagesApiServer() {
  return CommonsExecWebServerFactoryBean.builder()
    .classpath(cp -> cp
        .files("build/libs/messages-0.0.1-SNAPSHOT.jar")
    );
}
Note
While our @DynamicProperty examples use CommonsExecWebServer, the @DynamicProperty annotation works with any type of Bean.

Composed @DynamicProperty Annotations

@DynamicProperty is treated as a meta-annotation, so you can create composed annotations with it. For example, the following works the same as our example above:

MessageUrl.java
@Retention(RetentionPolicy.RUNTIME)
@DynamicProperty(name = "message.url", value = "'http://localhost:' + port")
public @interface MessageUrl {
}
Config.java
@Bean
@MessageUrl
static CommonsExecWebServerFactoryBean oauthServer() {
  return CommonsExecWebServerFactoryBean.builder()
    .classpath(cp -> cp
      .files("build/libs/authorization-server-0.0.1-SNAPSHOT.jar")
    );
}

Well Known Composed @DynamicProperty Annotations

This is a list of well known composed @DynamicProperty annotations.

@OAuth2ClientProviderIssuerUri

This provides a mapping to issuer-uri of the OAuth provider details.

  • name spring.security.oauth2.client.provider.${providerName}.issuer-uri with a default providerName of spring. The providerName can be overridden with the OAuth2ClientProviderIssuerUri.providerName property.

  • value 'http://127.0.0.1:' + port which can be overriden with the OAuth2ClientProviderIssuerUri.value property

Samples

Run TestOauth2LoginMain. This starts the oauth2-login sample and a Spring Authorization Server you assembled in the previous step.

You will be redirected to the authorization server. Log in using the username user and password password.

You are then redirected to the oauth2-login application.