spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.95k stars 40.65k forks source link

spring-boot.run.useTestClasspath does not take target/test-classes in account #36115

Open fjakop opened 1 year ago

fjakop commented 1 year ago

spring-boot-maven-plugin v3.1.0 OpenJDK 20

When I put an @SpringBootApplication annotated class into the test folder src/test/java and activate useTestClasspath the class cannot be loaded in start goal.

[INFO] --- spring-boot-maven-plugin:3.1.0:start (start-apidoc-application) @ mymodule ---
[INFO] Attaching agents: []
Fehler: Hauptklasse de.my.OpenApiApplication konnte nicht gefunden oder geladen werden
Ursache: java.lang.ClassNotFoundException: de.my.OpenApiApplication

I have verified that the compiled class is present at target/test-classes/de.my. Putting the package and the class to src/main/java works.

pom.xml

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <useTestClasspath>true</useTestClasspath>
                <mainClass>de.my.OpenApiApplication</mainClass>
            </configuration>
            <executions>
                <execution>
                    <id>start-apidoc-application</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>start</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

The docs state in https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#goals-start-parameters-details-useTestClasspath

Flag to include the test classpath when running.

I would expect target/test-classes to be part of the test classpath so the classesthere should be taken into account.

wilkinsona commented 1 year ago

Looking at the original PR, it was intentional that only test dependencies are included. That said, the PR cites Jetty's Maven plugin as a source of inspiration and even around the time of Jetty 7 it has a property named useTestScope that includes test classes and dependencies:

<useTestScope> – If true, the classes from and dependencies of scope "test" are placed first on the classpath. By default this is false

That continues to be the case in Jetty 11.

The new test-run goal the we added in 3.1 always includes test classes on the classpath. If we change the behavior of useTestClasspath to do that too, run using the test classpath would overlap with test-run.

I'd like to discuss this with the rest of the team. We need to decide:

  1. if start should include test classes on the classpath when useTestClasspath is true.
  2. if run should include test classes on the classpath when useTestClasspath is true or if we should deprecate its useTestClasspath setting in favor of test-run.
  3. if we should treat any changes that we decide to make as a bug or as an enhancement.
fjakop commented 1 year ago

Maybe a little more background for the use case: We generate the openapi specs from our application with the springdoc-maven-plugin, so we need a startet, yet not blocking, application therefore run is not useable here.

Since we do not like to bootstrap all application layers (services, repositories), which would require a full-blown application.yaml and maybe external systems, we just like to fire up the rest layer and mock all dependency beans. This works very well when placing a stripped down starter class in the test classpath, since we can use mockito for the purpose of mocking the dependency beans.

philwebb commented 1 year ago

Given that we think the current behavior is intentional, we think we should add a new feature to support more options. We think it will be possible to have <useTestClasspath>true</useTestClasspath> and <useTestClasspath>false</useTestClasspath> work for back compatibility but also introduce an enum for different modes. We'd have something like OFF, DEPENDENCIES and ALL for the various different modes.

pbarton-andovercos commented 1 year ago

Have you all thought about maybe adding two more goals: test-start in the pre-integration-test phase and test-stop in the post-integration-test phase to align with the new test-run goal? This would align with the start, run, stop goals but for tests that use a main() for a Local Launcher in testClassesDirectory.

package my.api.tests;

import my.api.MyApplication;
import my.api.tests.config.TestContainerConfiguration;
import org.springframework.boot.SpringApplication;

public class LocalMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main)
                .with(TestContainerConfiguration.class)
                .run(args);
    }

}

The test-run goal works great with testcontainers, however we cannot use this inside the build lifecycle as we cannot stop it automatically.